<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>choi-ss-choco.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Fri, 12 Dec 2025 13:47:37 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>choi-ss-choco.log</title>
            <url>https://images.velog.io/images/choi-ss-choco/profile/00220b16-dee5-495c-9306-2041adf43da2/20210315_미닷.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. choi-ss-choco.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/choi-ss-choco" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[처음부터 배우는 리액트 네이티브] -2. 로그인/회원가입 구현하기]]></title>
            <link>https://velog.io/@choi-ss-choco/%EC%B2%98%EC%9D%8C%EB%B6%80%ED%84%B0-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C-2.-%EB%A1%9C%EA%B7%B8%EC%9D%B8%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@choi-ss-choco/%EC%B2%98%EC%9D%8C%EB%B6%80%ED%84%B0-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C-2.-%EB%A1%9C%EA%B7%B8%EC%9D%B8%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 12 Dec 2025 13:47:37 GMT</pubDate>
            <description><![CDATA[<p>깃허브 레포지토리이다.
<a href="https://github.com/choi-day/react-native-simple-chat">https://github.com/choi-day/react-native-simple-chat</a></p>
<h1 id="프로젝트-준비">프로젝트 준비</h1>
<h2 id="내비게이션">내비게이션</h2>
<p>화면 이동에 필요한 내비게이션을 이용하여 화면을 구성한다.</p>
<pre><code class="language-bash">npm install @react-navigation/native</code></pre>
<pre><code class="language-bash">expo install react-native-gesture-handler react-native-reanimated react-native-
screens react-native-safe-area-context @react-native-community/masked-view</code></pre>
<pre><code class="language-bash">npm install @react-navigation/stack @react-navigation/bottom-tabs</code></pre>
<h2 id="라이브러리">라이브러리</h2>
<ul>
<li><p>styled-component: 스타일 작성을 위한 라이브러리</p>
<pre><code class="language-bash">  npm install styled-components</code></pre>
</li>
<li><p>pro-types: 타입 확인을 위한 라이브러리</p>
<pre><code class="language-bash">  npm install styled-components prop-types</code></pre>
</li>
<li><p>expo-image-picker: 기기의 사진이나 영상을 가져올 수 있도록 도움</p>
<pre><code class="language-bash"></code></pre>
</li>
<li><p>moment: 시간과 관련된 많은 기능을 제공</p>
</li>
<li><p>react-naitive-keyboard-aware-scroll-view: 키보드가 화면을 가리면서 생기는 불편한 점을 해결하기 위해 사용되는 라이브러리</p>
</li>
<li><p>react-naitive-gifted-chat: 메세지를 주고받는 채팅 화면을 쉽게 구현할 수 있도록 돕는 라이브러리</p>
</li>
</ul>
<h2 id="프로젝트-구조">프로젝트 구조</h2>
<pre><code class="language-perl">react-native-simple-chat
│
├── assets
│   ├── icon.png
│   └── splash.png
│
├── src
│   ├── components
│   │
│   ├── contexts
│   │
│   ├── navigations
│   │
│   ├── screens
│   │
│   ├── utils
│   │
│   ├── App.js
│   └── theme.js
│
└── App.js</code></pre>
<ul>
<li>components: 컴포넌트 파일 관리</li>
<li>contexts: Context API 파일 관리</li>
<li>navigations: 내비게이션 파일 관리</li>
<li>screens: 화면 파일 관리</li>
<li>utils: 프로젝트에서 이용할 기타 기능 관리</li>
</ul>
<p>theme.js - 공통으로 사용할 색을 정의하고 배경색과 글자색을 미리 정의했다.</p>
<pre><code class="language-perl">const colors = {
  white: &#39;#ffffff&#39;,
  black: &#39;#000000&#39;,
  grey_0: &#39;#d5d5d5&#39;,
  grey_1: &#39;#a6a6a6&#39;,
  red: &#39;#e84118&#39;,
  blue: &#39;#3679fe&#39;,
};

export const theme = {
  background: colors.white,
  text: colors.black
}</code></pre>
<p>src/App.js - 컴포넌트에 정의된 theme를 사용할 수 있도록 한다.</p>
<pre><code class="language-perl">import React from &#39;react&#39;;
import { StatusBar } from &#39;react-native&#39;;
import { ThemeProvider } from &#39;styled-components/native&#39;;
import { theme } from &#39;./theme&#39;;

const App = () =&gt; {
    return (
        &lt;ThemeProvider theme={theme}&gt;
        &lt;StatusBar barStyle=&quot;dark-content&quot; /&gt;
        &lt;/ThemeProvider&gt;
    );
};

export default App;</code></pre>
<p>App.js - App컴포넌트가 메인 파일이 되도록 한다</p>
<pre><code class="language-perl">import App from &#39;./src/App&#39;;

export default App;</code></pre>
<p>이 프로젝트에서는 서버 대신 파이어베이스를 이용한다. 설정과 관련된 부분은 아래 글을 참고하자. </p>
<p><a href="https://velog.io/@choi-ss-choco/%EC%B2%98%EC%9D%8C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C-%EC%B1%84%ED%8C%85-%EC%96%B4%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%A7%8C%EB%93%A4%EA%B8%B0-1.-%ED%8C%8C%EC%9D%B4%EC%96%B4%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%84%A4%EC%A0%95">파이어베이스 설정</a></p>
<h2 id="앱-아이콘과-로딩화면">앱 아이콘과 로딩화면</h2>
<p>src/App.js</p>
<pre><code class="language-perl">import React, { useState } from &#39;react&#39;;
import { StatusBar, Image } from &#39;react-native&#39;;
import AppLoading from &#39;expo-app-loading&#39;;
import { Asset } from &#39;expo-asset&#39;;
import * as Font from &#39;expo-font&#39;;
import { ThemeProvider } from &#39;styled-components/native&#39;;
import { theme } from &#39;./theme&#39;;

const cacheImages = images =&gt; {
  return images.map(image =&gt; {
    if (typeof image === &#39;string&#39;) {
      return Image.prefetch(image);
    } else {
      return Asset.fromModule(image).downloadAsync();
    }
  });
};

const cacheFonts = fonts =&gt; {
  return fonts.map(font =&gt; Font.loadAsync(font));
};

const App = () =&gt; {
  const [isReady, setIsReady] = useState(false);

  const _loadAssets = async () =&gt; {
    const imageAssets = cacheImages([require(&#39;../assets/splash.png&#39;)]);
    const fontAssets = cacheFonts([]);

    await Promise.all([...imageAssets, ...fontAssets]);
  };

  return isReady ? (
    &lt;&gt;
      &lt;ThemeProvider theme={theme}&gt;
        &lt;StatusBar barStyle=&quot;dark-content&quot; /&gt;
      &lt;/ThemeProvider&gt;
    &lt;/&gt;
  ) : (
    &lt;AppLoading
      startAsync={_loadAssets}
      onFinish={() =&gt; setIsReady(true)}
      onError={console.warn}
    /&gt;
  );
};

export default App;</code></pre>
<ul>
<li><p>_loadAssets함수</p>
<p>  : 이미지나 폰트를 미리 불러와 느리게 적용되는 문제를 개선 유저 경험 개선에 큰 도움을 준다</p>
<ul>
<li><p>스플래시 이미지 로딩</p>
</li>
<li><p>(필요하다면) 폰트 로딩</p>
</li>
<li><p>Promise.all로 모든 로딩이 끝날 때까지 기다림</p>
<p>로딩이 끝나야 실제 화면이 렌더링된다.</p>
</li>
</ul>
</li>
<li><p>cacheImages 함수</p>
<p>  이미지를 미리 불러오는 함수이다.</p>
<ul>
<li><p>URL 형태의 문자열 → Image.prefetch</p>
</li>
<li><p>require로 불러오는 로컬 이미지 → Asset.fromModule().downloadAsync()</p>
<p>둘 다 Promise를 반환하기 때문에 나중에 Promise.all()로 처리할 수 있다.</p>
</li>
</ul>
</li>
<li><p>cacheFonts</p>
<p>  폰트도 배열로 받아서 Promise 배열로 만들어 준다.
  필요한 폰트가 있다면 여기에 추가해서 로딩하면 된다.</p>
</li>
</ul>
<aside>

<p>책에서는 expo에서 AppLoading을 import 하고 있으나 Expo SDK 45 이후로 AppLoading은 expo에서 제거되었다. </p>
<pre><code class="language-perl">npm install expo-app-loading</code></pre>
<p>위와 같이 설치 후 &#39;expo-app-loading’으로 바꿔주면 정상적으로 스플래쉬 화면이 뜬다</p>
</aside>

<h2 id="인증화면">인증화면</h2>
<p>로그인 및 회원가입 기능을 구현한다. 로그인 화면에서 이메일과 비밀번호를 입력받고 회원가입시 서비스에서 이용할 이름과 프로필 사진을 받는다.</p>
<h3 id="네비게이션">네비게이션</h3>
<p>로그인 화면과 회원가입 화면을 구현한다</p>
<p>screens/Login.js</p>
<pre><code class="language-perl">import React from &#39;react&#39;;
import styled from &#39;styled-components/native&#39;;
import { Text, Button } from &#39;react-native&#39;;

const Container = styled.View`
    flex: 1;
    justify-content: center;
    align-items: center;
    background-color: ${({ theme }) =&gt; theme.background};
`;

const Login = ({ navigation }) =&gt; {
    return (
        &lt;Container&gt;
            &lt;Text style={{ fontSize: 30 }}&gt;Login Screen&lt;/Text&gt;
            &lt;Button title=&quot;Signup&quot; onPress={() =&gt; navigation.navigate(&#39;Signup&#39;)} /&gt;
        &lt;/Container&gt;
    );
};

export default Login;</code></pre>
<p>screens/Signup.js</p>
<pre><code class="language-perl">import React from &#39;react&#39;;
import styled from &#39;styled-components/native&#39;;
import { Text } from &#39;react-native&#39;;

const Container = styled.View`
  flex: 1;
  justify-content: center;
  align-items: center;
  background-color: ${({ theme }) =&gt; theme.background};
`;

const Signup = () =&gt; {
  return (
    &lt;Container&gt;
      &lt;Text style={{ fontSize: 30 }}&gt;Signup Screen&lt;/Text&gt;
    &lt;/Container&gt;
  );
};

export default Signup;</code></pre>
<p>screens/index.js</p>
<pre><code class="language-perl">import Login from &#39;./Login&#39;;
import Signup from &#39;./Signup&#39;;

export { Login, Signup};</code></pre>
<p>화면을 한 곳에서 관리하기 위해 필요한 파일이다.</p>
<p>navigationsAuthStack.js</p>
<pre><code class="language-perl">import React, { useContext } from &#39;react&#39;;
import { ThemeContext } from &#39;styled-components/native&#39;;
import { createStackNavigator } from &#39;@react-navigation/stack&#39;;
import { Login, Signup } from &#39;../screens&#39;;

const Stack = createStackNavigator();

const AuthStack = () =&gt; {
  const theme = useContext(ThemeContext);
  return (
    &lt;Stack.Navigator
      initialRouteName=&quot;Login&quot;
      screenOptions={{
        headerTitleAlign: &#39;center&#39;,
        cardStyle: { backgroundColor: theme.background },
      }}
    &gt;
      &lt;Stack.Screen name=&quot;Login&quot; component={Login} /&gt;
      &lt;Stack.Screen name=&quot;Signup&quot; component={Signup} /&gt;
    &lt;/Stack.Navigator&gt;
  );
};

export default AuthStack;</code></pre>
<aside>

<p><strong>📝 전체 동작 요약</strong></p>
<ol>
<li>createStackNavigator()로 만든 Stack 안에 로그인과 회원가입 두 화면을 등록한다.</li>
<li>AuthStack이 사용되면 처음에는 항상 Login 화면이 보인다(initialRouteName).</li>
<li>Login 화면에서 navigation.navigate(&#39;Signup&#39;)을 호출하면 Signup 화면으로, Signup에서 navigation.navigate(&#39;Login&#39;)을 호출하면 다시 Login으로 이동한다.</aside>

</li>
</ol>
<p>navigations/index.js</p>
<pre><code class="language-perl">import React from &#39;react&#39;;
import { NavigationContainer } from &#39;@react-navigation/native&#39;;
import AuthStack from &#39;./AuthStack&#39;;

const Navigation = () =&gt; {
  return (
    &lt;NavigationContainer&gt;
      &lt;AuthStack /&gt;
    &lt;/NavigationContainer&gt;
  );
};

export default Navigation;</code></pre>
<aside>

<p><strong>📝 전체 동작 요약</strong></p>
<ol>
<li>NavigationContainer가 앱의 전체 네비게이션 상태를 관리한다.</li>
<li>그 안에서 AuthStack이 실행되며 Login → Signup 같은 화면 이동을 담당한다.</li>
<li>앱이 실행되면 AuthStack의 initialRouteName 설정에 따라 Login 화면이 처음 보여진다.</li>
<li>다른 화면은 navigation.navigate(‘Signup’) 같은 코드로 이동할 수 있다.</aside>

</li>
</ol>
<h3 id="로그인화면">로그인화면</h3>
<p>components/Image.js</p>
<pre><code class="language-perl">import React from &#39;react&#39;;
import styled from &#39;styled-components/native&#39;;
import PropTypes from &#39;prop-types&#39;;

const Container = styled.View`
  align-self: center;
  margin-bottom: 30px;
`;

const StyledImage = styled.Image`
  background-color: ${({ theme }) =&gt; theme.imageBackground};
  width: 100px;
  height: 100px;
`;

const Image = ({ uri, imageStyle }) =&gt; {
  return (
    &lt;Container&gt;
      &lt;StyledImage source={{ uri: uri }} style={imageStyle} /&gt;
    &lt;/Container&gt;
  );
};

Image.propTypes = {
  uri: PropTypes.string,
  imageStyle: PropTypes.object,
};

export default Image;</code></pre>
<aside>

<p><strong>📝 전체 동작 요약</strong></p>
<ol>
<li>Image 컴포넌트는 네트워크 이미지 URL(uri)을 받아 화면에 표시한다.</li>
<li>이미지는 StyledImage로 스타일링되어 크기(100×100)와 배경색(theme.imageBackground)이 적용된다.</li>
<li>Container는 이미지를 화면 중앙에 배치하고 아래쪽 여백을 준다.</li>
<li>imageStyle prop을 전달하면 기본 스타일 위에 추가 스타일을 덮어 적용할 수 있다.</aside>

</li>
</ol>
<p>components/Image.js</p>
<pre><code class="language-perl">import Image from &#39;./Image&#39;

export {Image}</code></pre>
<p>파이어베이스 Storage에 logo 파일을 업로드한다.</p>
<p>이름을 클릭하면 이미지파일 링크를 얻을 수 있다.</p>
<pre><code class="language-perl">service firebase.storage {
  match /b/{bucket}/o {
    match /logo.png {
      allow read;
    }
  }
}
</code></pre>
<p>규칙 또는 Rules탭으로 들어가 로그인하지 않아도 이미지에 접근할 수 있도록 수정한다.</p>
<p>잘 받아와지는지 궁금하다면 이미지 파일 링크를 외부에서 접속해본다. 이미지가 잘 뜬다면 적용이 된것이다.</p>
<p>utils/image.js</p>
<pre><code class="language-perl">const prefix = &#39;https://firebasestorage.googleapis.com/v0/b/react-native-simple-chat-469fb.firebasestorage.app/o&#39;;

export const images = {
    logo: `${prefix}/logo.png?alt=media`,
};</code></pre>
<p>이미지를 불러오는 코드이다</p>
<p>App.js</p>
<pre><code class="language-perl">const _loadAssets = async () =&gt; {
    const imageAssets = cacheImages([require(&#39;../assets/splash.png&#39;),
        ...Object.values(images),
    ]);
    const fontAssets = cacheFonts([]);</code></pre>
<p>파이어베이스의 이미지 파일도 함께 불러오도록 수정한다</p>
<p>Login.js</p>
<pre><code class="language-perl">const Login = ({ navigation }) =&gt; {
    return (
        &lt;Container&gt;
            &lt;Image uri={images.logo} imageStyle={{borderRadius: 8}}/&gt;
            &lt;Text style={{ fontSize: 30 }}&gt;Login Screen&lt;/Text&gt;
            &lt;Button title=&quot;Signup&quot; onPress={() =&gt; navigation.navigate(&#39;Signup&#39;)} /&gt;
        &lt;/Container&gt;
    );
};</code></pre>
<p>이미지를 로그인화면에 삽입한다.</p>
<p><img src="attachment:27b8aff5-1642-4b71-a9dd-cab32fc6c40f:Simulator_Screenshot_-_iPhone_16_Pro_Max_-_2025-12-12_at_12.24.23.png" alt="Simulator Screenshot - iPhone 16 Pro Max - 2025-12-12 at 12.24.23.png"></p>
<p>theme.js</p>
<pre><code class="language-perl">label: colors.grey_1,
inputPlaceholder: colors.grey_1,
inputBorder: colors.grey_1,</code></pre>
<p>input에 들어갈 색상을 정의해준다</p>
<p>compoenets/Input.js</p>
<pre><code class="language-perl">import React, { useState } from &#39;react&#39;;
import styled from &#39;styled-components/native&#39;;
import PropTypes from &#39;prop-types&#39;;

const Container = styled.View`
  flex-direction: column;
  width: 100%;
  margin: 10px 0;
`;

const Label = styled.Text`
  font-size: 14px;
  font-weight: 600;
  margin-bottom: 6px;
  color: ${({ theme, isFocused }) =&gt; (isFocused ? theme.text : theme.label)};
`;

const StyledTextInput = styled.TextInput.attrs(({ theme }) =&gt; ({
  placeholderTextColor: theme.inputPlaceholder,
}))`
  background-color: ${({ theme }) =&gt; theme.background};
  color: ${({ theme }) =&gt; theme.text};
  padding: 20px 10px;
  font-size: 16px;
  border: 1px solid
    ${({ theme, isFocused }) =&gt; (isFocused ? theme.text : theme.inputBorder)};
  border-radius: 4px;
`;

const Input = ({
  label,
  value,
  onChangeText,
  onSubmitEditing,
  onBlur,
  placeholder,
  isPassword,
  returnKeyType,
  maxLength,
}) =&gt; {
  const [isFocused, setIsFocused] = useState(false);

  return (
    &lt;Container&gt;
      &lt;Label isFocused={isFocused}&gt;{label}&lt;/Label&gt;
      &lt;StyledTextInput
        isFocused={isFocused}
        value={value}
        onChangeText={onChangeText}
        onSubmitEditing={onSubmitEditing}
        onFocus={() =&gt; setIsFocused(true)}
        onBlur={() =&gt; {
          setIsFocused(false);
          onBlur();
        }}
        placeholder={placeholder}
        secureTextEntry={isPassword}
        returnKeyType={returnKeyType}
        maxLength={maxLength}
        autoCapitalize=&quot;none&quot;
        autoCorrect={false}
        textContentType=&quot;none&quot; // iOS only
        underlineColorAndroid=&quot;transparent&quot; // Android only
      /&gt;
    &lt;/Container&gt;
  );
};

Input.defaultProps = {
  onBlur: () =&gt; {},
};

Input.propTypes = {
  label: PropTypes.string.isRequired,
  value: PropTypes.string.isRequired,
  onChangeText: PropTypes.func.isRequired,
  onSubmitEditing: PropTypes.func.isRequired,
  onBlur: PropTypes.func,
  placeholder: PropTypes.string,
  isPassword: PropTypes.bool,
  returnKeyType: PropTypes.oneOf([&#39;done&#39;, &#39;next&#39;]),
  maxLength: PropTypes.number,
};

export default Input;</code></pre>
<aside>

<p><strong>📝 Input 컴포넌트 전체 동작 요약</strong></p>
<ol>
<li>사용자가 입력창에 포커스하면(isFocused) 라벨 색상과 입력창(border 색상)이 강조 색(theme.text)으로 바뀐다.</li>
<li>포커스가 해제되면 onBlur()를 실행하고 다시 기본 색상으로 돌아간다.</li>
<li>입력값(value), 텍스트 변경(onChangeText), 제출(onSubmitEditing) 등은 부모에서 전달받아 처리한다.</li>
<li>비밀번호 입력 여부, placeholder, returnKeyType, maxLength 등의 설정을 지원한다.</aside>

</li>
</ol>
<p>Login.js</p>
<pre><code class="language-perl">const Login = ({ navigation }) =&gt; {
  const [email, setEmail] = useState(&#39;&#39;);
  const [password, setPassword] = useState(&#39;&#39;);

  return (
    &lt;Container&gt;
      &lt;Image url={images.logo} imageStyle={{ borderRadius: 8 }} /&gt;
      &lt;Input
        label=&quot;Email&quot;
        value={email}
        onChangeText={text =&gt; setEmail(text)}
        onSubmitEditing={() =&gt; {}}
        placeholder=&quot;Email&quot;
        returnKeyType=&quot;next&quot;
      /&gt;
      &lt;Input
        label=&quot;Password&quot;
        value={password}
        onChangeText={text =&gt; setPassword(text)}
        onSubmitEditing={() =&gt; {}}
        placeholder=&quot;Password&quot;
        returnKeyType=&quot;done&quot;
        isPassword
      /&gt;
    &lt;/Container&gt;
  );
};</code></pre>
<aside>

<p><strong>📝 수정된 동작 요약</strong></p>
<ol>
<li>이메일/패스워드 입력 UI 커스텀 Input 컴포넌트로 구현</li>
<li>입력 필드에 label·placeholder·returnKeyType·isPassword 등 다양한 속성을 추가하여 기능 향상됨</aside>

</li>
</ol>
<p>Login.js</p>
<pre><code class="language-perl">...

const passwordRef = useRef();

...

  return (
    &lt;Container&gt;
      &lt;Image url={images.logo} imageStyle={{ borderRadius: 8 }} /&gt;
      &lt;Input
        label=&quot;Email&quot;
        value={email}
        onChangeText={text =&gt; setEmail(text)}
        onSubmitEditing={() =&gt; passwordRef.current.focus()}
        placeholder=&quot;Email&quot;
        returnKeyType=&quot;next&quot;
      /&gt;

...

      &lt;Input
        ref = {password}
        label=&quot;Password&quot;</code></pre>
<aside>

<p><strong>📝 수정된 동작 요약</strong></p>
<ol>
<li>useRef()로 passwordRef를 만들고, Password <Input />에 ref={passwordRef}로 연결한다.</li>
<li>Email 입력칸에서 enter를 입력하면 passwordRef.current.focus()를 호출해서 자동으로 Password 입력칸으로 커서가 이동한다.</aside>

</li>
</ol>
<p>Input.js</p>
<pre><code class="language-perl">...
onBlur={() =&gt; {
  setIsFocused(false);
  if (typeof onBlur === &#39;function&#39;) {
    onBlur();
  }
}}
...</code></pre>
<p>갑자기 onBlur();가 작동하지 않아 에러가 떴다. onBlur prop이 없거나(undefined), 함수가 아닐 때도 크래시가 나지 않도록 방어 코드를 추가했다.</p>
<p>비밀번호를 입력할 때 키보드에 가려 보이지 않는다. 키보드 스크롤 뷰를 이용하여 사용성을 개선한다.</p>
<pre><code class="language-perl">npm install react-native-keyboard-aware-scroll-view</code></pre>
<p>react-native-keyboard-aware-scroll-view 를 설치한다</p>
<p>Login.js</p>
<pre><code class="language-perl">import { KeyboardAwareScrollView } from &#39;react-native-keyboard-aware-scroll-view&#39;;

...

return (
  &lt;KeyboardAwareScrollView
    contentContainerStyle={{ flex: 1 }}
    extraScrollHeight={20}
  &gt;
    &lt;Container&gt;
      ...
    &lt;/Container&gt;
  &lt;/KeyboardAwareScrollView&gt;
);</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li>KeyboardAwareScrollView를 사용해 키보드가 올라올 때 화면이 자동으로 스크롤되도록 수정함</li>
<li>입력창(Input)이 키보드에 가려지지 않고 항상 화면에 보이도록 UX 개선</li>
<li>extraScrollHeight 옵션을 통해 키보드와 입력창 사이 여유 공간을 추가함</li>
<li>로그인/회원가입 화면에서 키보드 대응 로직을 컴포넌트 외부가 아닌 레이아웃 레벨에서 처리하도록 구조 정리</aside>

</li>
</ol>
<p>사용자가 이메일을 틀린 형식으로 입력했다면 이메일을 다시 입력하라는 경고를 띄워야 한다. </p>
<p>theme.js</p>
<pre><code class="language-perl"> errorText: colors.red,</code></pre>
<p>오류 메세지 색상을 정의한다</p>
<p>utils/common.js</p>
<pre><code class="language-perl">export const validateEmail = email =&gt; {
    const regex = /^[0-9?A-z0-9?]+(\.[0-9?A-z0-9?]+)*@[0-9?A-z]+\.[A-z]{2,}(\.[A-z]{0,3})?$/;
    return regex.test(email);
};

export const removeWhitespace = text =&gt; {
    const regex = /\s/g;
    return text.replace(regex, &#39;&#39;);
};</code></pre>
<aside>

<h3 id="📝-이메일-유효성-검사-추가-동작-요약"><strong>📝 이메일 유효성 검사 추가 동작 요약</strong></h3>
<ol>
<li>이메일 형식 검증 로직 추가<ul>
<li>정규표현식을 사용해 이메일 문자열이 올바른 형식인지 검사하는 validateEmail 함수가 새로 구현됨</li>
<li>입력된 이메일이 <a href="mailto:example@domain.com">example@domain.com</a> 과 같은 형태인지 boolean 값으로 판단 가능</li>
</ul>
</li>
<li>입력값 공백 제거 유틸 함수 추가<ul>
<li>문자열 내 모든 공백을 제거하는 removeWhitespace 함수가 추가됨</li>
<li>이메일·비밀번호 입력 시 의도치 않은 공백으로 인한 오류를 사전에 방지</aside>

</li>
</ul>
</li>
</ol>
<p>Login.js</p>
<pre><code class="language-perl">...
import { Image, Input } from &#39;../components&#39;;
import { images } from &#39;../utils/images&#39;;
import { validateEmail, removeWhitespace } from &#39;../utils/common&#39;;

...

const ErrorText = styled.Text`
  align-self: flex-start;
  width: 100%;
  height: 20px;
  margin-bottom: 10px;
  line-height: 20px;
  color: ${({ theme }) =&gt; theme.errorText};
`;

const Login = ({ navigation }) =&gt; {
  const [email, setEmail] = useState(&#39;&#39;);
  const [password, setPassword] = useState(&#39;&#39;);
  const [errorMessage, setErrorMessage] = useState(&#39;&#39;);

  const passwordRef = useRef(null);

  const _handleEmailChange = email =&gt; {
    const changedEmail = removeWhitespace(email);
    setEmail(changedEmail);
    setErrorMessage(validateEmail(changedEmail) ? &#39;&#39; : &#39;Please verify your email.&#39;);
  };

  const _handlePasswordChange = password =&gt; {
    setPassword(removeWhitespace(password));
  };

  return (
    &lt;KeyboardAwareScrollView contentContainerStyle={{ flex: 1 }} extraScrollHeight={20}&gt;
      &lt;Container&gt;
        &lt;Image url={images.logo} imageStyle={{ borderRadius: 8 }} /&gt;

        &lt;Input
          label=&quot;Email&quot;
          value={email}
          onChangeText={_handleEmailChange}
          onSubmitEditing={() =&gt; passwordRef.current?.focus()}
          placeholder=&quot;Email&quot;
          returnKeyType=&quot;next&quot;
        /&gt;

        &lt;Input
          ref={passwordRef}
          label=&quot;Password&quot;
          value={password}
          onChangeText={_handlePasswordChange}
          onSubmitEditing={() =&gt; {}}
          placeholder=&quot;Password&quot;
          returnKeyType=&quot;done&quot;
          isPassword
        /&gt;

        &lt;ErrorText&gt;{errorMessage}&lt;/ErrorText&gt;
      &lt;/Container&gt;
    &lt;/KeyboardAwareScrollView&gt;
  );
};
...</code></pre>
<aside>

<p><strong>📝 수정된 동작 요약</strong></p>
<ol>
<li>이메일 입력값이 바뀔 때마다 removeWhitespace()로 공백을 제거한 뒤 setEmail()로 상태를 갱신한다.</li>
<li>공백 제거된 이메일을 validateEmail()로 검사해서, 올바르면 errorMessage를 &#39;&#39;로 비우고 아니면 &quot;Please verify your email.&quot;을 세팅한다.</li>
<li>비밀번호도 입력 시 removeWhitespace()로 공백을 제거해서 setPassword()에 저장한다.</li>
<li>화면 하단에 <ErrorText>{errorMessage}</ErrorText>를 추가해서, 이메일 형식이 틀리면 즉시 빨간 안내문이 보이도록 한다.</aside>

</li>
</ol>
<p>로그인 버튼을 구현해보자</p>
<p>theme.js</p>
<pre><code class="language-perl">  buttonBackground: colors.blue,
  buttonTitle: colors.white,
  buttonUnfilledTitle: colors.blue,</code></pre>
<p>버튼과 관련된 색상을 정의한다</p>
<p>components/Button.js</p>
<pre><code class="language-perl">import React from &#39;react&#39;;
import styled from &#39;styled-components/native&#39;;
import PropTypes from &#39;prop-types&#39;;

const TRANSPARENT = &#39;transparent&#39;;

const Container = styled.TouchableOpacity`
  background-color: ${({ theme, isFilled }) =&gt;
    isFilled ? theme.buttonBackground : TRANSPARENT};
  align-items: center;
  border-radius: 4px;
  width: 100%;
  padding: 10px;
`;

const Title = styled.Text`
  height: 30px;
  line-height: 30px;
  font-size: 16px;
  color: ${({ theme, isFilled }) =&gt;
    isFilled ? theme.buttonTitle : theme.buttonUnfilledTitle};
`;

const Button = ({ containerStyle, title, onPress, isFilled }) =&gt; {
  return (
    &lt;Container style={containerStyle} onPress={onPress} isFilled={isFilled}&gt;
      &lt;Title isFilled={isFilled}&gt;{title}&lt;/Title&gt;
    &lt;/Container&gt;
  );
};

Button.defaultProps = {
  isFilled: true,
};

Button.propTypes = {
  containerStyle: PropTypes.object,
  title: PropTypes.string,
  onPress: PropTypes.func.isRequired,
  isFilled: PropTypes.bool,
};

export default Button;</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-1"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li>styled-components를 활용해 isFilled 속성에 따라 배경색을 채울지 투명하게 할지 결정하는 동적 스타일링을 적용</li>
<li>Title 텍스트 색상 또한 isFilled 속성에 따라 테마에 맞춰 자동으로 변경되도록 설정함</li>
<li>defaultProps를 통해 isFilled 값을 별도로 지정하지 않으면 기본적으로 채워진 버튼(true)으로 렌더링되도록 처리함</aside>

</li>
</ol>
<p>버튼 컴포넌트를 컴포넌트의 index.js에 추가하고 로그인 화면에 적용한다</p>
<p>Login.js</p>
<pre><code class="language-perl">import { Image, Input, Button } from &#39;../components&#39;;

...

  const _handleLoginButtonPress = () =&gt; {};

  return (
    &lt;KeyboardAwareScrollView
      contentContainerStyle={{ flex: 1 }}
      extraScrollHeight={20}
    &gt;
      &lt;Container&gt;
        ...
        &lt;Input
          label=&quot;Password&quot;
          value={password}
          onChangeText={_handlePasswordChange}
          onSubmitEditing={_handleLoginButtonPress}
          placeholder=&quot;Password&quot;
          returnKeyType=&quot;done&quot;
          isPassword
        /&gt;

        &lt;ErrorText&gt;{errorMessage}&lt;/ErrorText&gt;
        &lt;Button title=&quot;Login&quot; onPress={_handleLoginButtonPress} /&gt;
        &lt;Button
          title=&quot;Sign up with email&quot;
          onPress={() =&gt; navigation.navigate(&#39;Signup&#39;)}
          isFilled={false}
        /&gt;
      &lt;/Container&gt;
    &lt;/KeyboardAwareScrollView&gt;
  );</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-2"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li>커스텀 Button 컴포넌트를 import하여 로그인과 회원가입 버튼을 화면에 추가함</li>
<li>로그인 버튼을 눌렀을 때 실행될 이벤트 핸들러 함수를 정의하고 버튼 onPress 이벤트에 연결함</li>
<li>비밀번호 입력창에서 작성을 마치고 키보드의 완료 버튼을 누를 때도 로그인 함수가 실행되도록 연결함</li>
<li>회원가입 버튼을 누르면 Signup 화면으로 이동하는 내비게이션 로직을 작성함</li>
<li>isFilled 속성을 false로 설정하여 회원가입 버튼이 배경색 없이 테두리만 있는 스타일로 렌더링되도록 함</aside>

</li>
</ol>
<p>Login.js</p>
<pre><code class="language-perl">import React, { useState, useRef, useEffect } from &#39;react&#39;;

...

  const [disabled, setDisabled] = useState(true);

  useEffect(() =&gt; {
    setDisabled(!(email &amp;&amp; password &amp;&amp; !errorMessage));
  }, [email, password, errorMessage]);

...

        &lt;ErrorText&gt;{errorMessage}&lt;/ErrorText&gt;
        &lt;Button
          title=&quot;Login&quot;
          onPress={_handleLoginButtonPress}
          disabled={disabled}
        /&gt;
        &lt;Button
          title=&quot;Sign up with email&quot;
          onPress={() =&gt; navigation.navigate(&#39;Signup&#39;)}
          isFilled={false}
        /&gt;
      &lt;/Container&gt;
    &lt;/KeyboardAwareScrollView&gt;
  );</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-3"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li><code>disabled</code> 상태 변수를 추가하여 버튼의 활성화/비활성화 상태를 관리함</li>
<li><code>useEffect</code> 훅을 사용하여 이메일(<code>email</code>), 비밀번호(<code>password</code>) 입력 여부와 에러 메시지(<code>errorMessage</code>) 존재 여부를 감시함</li>
<li>이메일과 비밀번호가 모두 입력되고 에러 메시지가 없을 때만 버튼이 활성화되도록 로직을 구현함</li>
<li>로그인 버튼 컴포넌트에 <code>disabled</code> props를 전달하여 조건 불충족 시 버튼을 누를 수 없도록 처리함</aside>

</li>
</ol>
<p>Button.js</p>
<pre><code class="language-perl">...

const Container = styled.TouchableOpacity`
  ...
  opacity: ${({ disabled }) =&gt; (disabled ? 0.5 : 1)};
`;

...

const Button = ({
  containerStyle,
  title,
  onPress,
  isFilled,
  disabled,
}) =&gt; {
  return (
    &lt;Container
      style={containerStyle}
      onPress={onPress}
      isFilled={isFilled}
      disabled={disabled}
    &gt;
      &lt;Title isFilled={isFilled}&gt;{title}&lt;/Title&gt;
    &lt;/Container&gt;
  );
};

Button.defaultProps = {
  isFilled: true,
  disabled: false,
};

Button.propTypes = {
  ...
  disabled: PropTypes.bool,
};

export default Button;</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-4"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li>Button 컴포넌트에 disabled 속성을 처리하는 스타일과 로직을 추가함</li>
<li>disabled 상태가 true일 경우 버튼의 투명도(opacity)를 0.5로 설정하여 비활성화된 시각적 효과를 줌</li>
<li>TouchableOpacity의 disabled 속성에 값을 전달하여 클릭 이벤트가 발생하지 않도록 막음</li>
<li>PropTypes에 disabled 타입을 boolean으로 정의하고 기본값을 false로 설정함</aside>

</li>
</ol>
<p>스택 네비게이션을 사용하기때문에 헤더가 있어야 한다.(이전 화면으로 돌아가기 위함)</p>
<p>theme.js</p>
<pre><code class="language-perl">headerTintColor: colors.black,</code></pre>
<p>헤더에서 사용할 색상을 정의한다</p>
<p>AuthStack.js</p>
<pre><code class="language-perl">...

    &lt;Stack.Navigator
      ...
    &gt;
      &lt;Stack.Screen
        name=&quot;Login&quot;
        component={Login}
        options={{ headerShown: false }}
      /&gt;
      &lt;Stack.Screen name=&quot;Signup&quot; component={Signup} /&gt;
    &lt;/Stack.Navigator&gt;

...</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-5"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li>Login 화면의 옵션에 headerShown 속성을 false로 설정하여 헤더를 숨김</li>
<li>내비게이션 스택에서 로그인 화면만 상단 헤더가 보이지 않도록 UI를 수정함</li>
<li>나머지 화면(예: Signup)은 기존대로 헤더가 유지됨</aside>

</li>
</ol>
<p>AuthStack.js</p>
<pre><code class="language-perl">...

    &lt;Stack.Navigator
      initialRouteName=&quot;Login&quot;
      screenOptions={{
        headerTitleAlign: &#39;center&#39;,
        cardStyle: { backgroundColor: theme.background },
        headerTintColor: theme.headerTintColor,
      }}
    &gt;
      &lt;Stack.Screen
        name=&quot;Login&quot;
        component={Login}
        options={{ headerShown: false }}
      /&gt;
      &lt;Stack.Screen
        name=&quot;Signup&quot;
        component={Signup}
        options={{ headerBackTitleVisible: false }}
      /&gt;
    &lt;/Stack.Navigator&gt;

...</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-6"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li>AuthStack 내비게이션의 공통 옵션에 headerTintColor를 설정하여 헤더 아이콘 및 텍스트 색상을 테마에 맞춤</li>
<li>Signup 화면의 옵션에 headerBackTitleVisible을 false로 설정하여 뒤로가기 버튼 옆의 타이틀(이전 화면 이름)을 숨김</li>
<li>기존에는 한 줄로 작성되어 있던 Signup 화면 설정을 가독성을 위해 여러 줄로 분리하여 옵션을 명확히 함</aside>

</li>
</ol>
<p>아이폰의 노치디자인에 가려 헤더가 안 보일 수 있다. UI를 개선해보자.</p>
<p>Login.js</p>
<pre><code class="language-perl">import { useSafeAreaInsets } from &#39;react-native-safe-area-context&#39;;

const Container = styled.View`
  ...
  padding: 0 20px;
  padding-top: ${({ insets: { top } }) =&gt; top}px;
  padding-bottom: ${({ insets: { bottom } }) =&gt; bottom}px;
`;

...

const Login = ({ navigation }) =&gt; {
  const insets = useSafeAreaInsets();
  ...
  return (
    &lt;KeyboardAwareScrollView ...&gt;
      &lt;Container insets={insets}&gt;
        ...
      &lt;/Container&gt;
    &lt;/KeyboardAwareScrollView&gt;
  );
};</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-7"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li>react-native-safe-area-context의 useSafeAreaInsets 훅을 도입하여 노치 디자인이나 홈바 영역의 안전한 여백 값을 가져옴</li>
<li>기존의 고정된 padding 값을 제거하고 상단(Top)과 하단(Bottom)에 디바이스별 안전 영역(Inset)만큼의 패딩을 동적으로 부여함</li>
<li>Container 컴포넌트에 insets 값을 props로 전달하여 스타일 내부에서 해당 값을 계산하도록 수정함</aside>

</li>
</ol>
<p>회원가입시 이미지를 원형으로 보이게 할 예정이다.</p>
<p>Image.js</p>
<pre><code class="language-perl">const StyledImage = styled.Image`
  background-color: ${({ theme }) =&gt; theme.imageBackground};
  width: 100px;
  height: 100px;
  border-radius: ${({ rounded }) =&gt; (rounded ? 50 : 0)}px;
`;

const Image = ({ uri, imageStyle, rounded }) =&gt; {
  return (
    &lt;Container&gt;
      &lt;StyledImage source={{ uri: uri }} style={imageStyle} rounded={rounded} /&gt;
    &lt;/Container&gt;
  );
};

Image.defaultProps = {
  rounded: false,
};

Image.propTypes = {
  uri: PropTypes.string,
  imageStyle: PropTypes.object,
  rounded: PropTypes.bool,
};

export default Image;</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-8"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li>Image 컴포넌트에 rounded 속성을 추가하여 이미지를 원형으로 렌더링할 수 있는 기능을 구현함</li>
<li>StyledImage 스타일 정의에서 rounded 값이 true일 경우 border-radius를 50px로 설정하여 100x100 크기의 이미지를 완벽한 원형으로 만듦</li>
<li>컴포넌트의 props로 rounded를 받아 하위 스타일 컴포넌트에 전달하도록 수정함</li>
<li>defaultProps를 통해 rounded 속성의 기본값을 false로 설정하여, 별도 지정이 없으면 사각형 이미지가 출력되도록 함</li>
<li>PropTypes에 rounded 속성을 boolean 타입으로 정의하여 타입 안정성을 확보함</aside>

</li>
</ol>
<p>Signup.js</p>
<pre><code class="language-perl">import React, { useState, useRef, useEffect } from &#39;react&#39;;
import { KeyboardAwareScrollView } from &#39;react-native-keyboard-aware-scroll-view&#39;;
import { Image, Input, Button } from &#39;../components&#39;;
import { validateEmail, removeWhitespace } from &#39;../utils/common&#39;;

const ErrorText = styled.Text`
  align-items: flex-start;
  width: 100%;
  height: 20px;
  margin-bottom: 10px;
  line-height: 20px;
  color: ${({ theme }) =&gt; theme.errorText};
`;

const Signup = ({ navigation }) =&gt; {
  const [name, setName] = useState(&#39;&#39;);
  const [email, setEmail] = useState(&#39;&#39;);
  const [password, setPassword] = useState(&#39;&#39;);
  const [passwordConfirm, setPasswordConfirm] = useState(&#39;&#39;);
  const [errorMessage, setErrorMessage] = useState(&#39;&#39;);
  const [disabled, setDisabled] = useState(true);

  const emailRef = useRef(null);
  const passwordRef = useRef(null);
  const passwordConfirmRef = useRef(null);

  useEffect(() =&gt; {
    let _errorMessage = &#39;&#39;;
    if (!name) {
      _errorMessage = &#39;Please enter your name.&#39;;
    } else if (!validateEmail(email)) {
      _errorMessage = &#39;Please verify your email.&#39;;
    } else if (password.length &lt; 6) {
      _errorMessage = &#39;The password must contain 6 characters at least.&#39;;
    } else if (password !== passwordConfirm) {
      _errorMessage = &#39;Passwords need to match.&#39;;
    } else {
      _errorMessage = &#39;&#39;;
    }
    setErrorMessage(_errorMessage);
  }, [name, email, password, passwordConfirm]);

  useEffect(() =&gt; {
    setDisabled(!(name &amp;&amp; email &amp;&amp; password &amp;&amp; passwordConfirm &amp;&amp; !errorMessage));
  }, [name, email, password, passwordConfirm, errorMessage]);

  const _handleSignupButtonPress = () =&gt; {
    // TODO: signup logic
  };

  return (
    &lt;KeyboardAwareScrollView ...&gt;
      &lt;Container&gt;
        &lt;Image rounded /&gt;
        &lt;Input ... label=&quot;Name&quot; value={name} ... /&gt;
        &lt;Input ... label=&quot;Email&quot; value={email} ... /&gt;
        &lt;Input ... label=&quot;Password&quot; value={password} ... /&gt;
        &lt;Input ... label=&quot;Password Confirm&quot; value={passwordConfirm} ... /&gt;

        &lt;ErrorText&gt;{errorMessage}&lt;/ErrorText&gt;
        &lt;Button title=&quot;Signup&quot; onPress={_handleSignupButtonPress} disabled={disabled} /&gt;
      &lt;/Container&gt;
    &lt;/KeyboardAwareScrollView&gt;
  );
};</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-9"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li>회원가입 화면(Signup.js)의 전체 UI와 유효성 검사 로직을 구현함</li>
<li>이름, 이메일, 비밀번호, 비밀번호 확인을 입력받기 위한 상태(State)와 Input 컴포넌트들을 배치함</li>
<li><code>useEffect</code>를 사용하여 입력값이 변경될 때마다 실시간으로 유효성 검사를 수행하고 에러 메시지를 업데이트함</li>
<li>모든 필드가 유효하고 에러 메시지가 없을 경우에만 회원가입 버튼이 활성화되도록 <code>disabled</code> 상태를 관리함</li>
<li><code>useRef</code>를 활용하여 엔터키 입력 시 다음 입력창으로 포커스가 자동으로 이동하도록 사용자 경험(UX)을 개선함</aside>

</li>
</ol>
<p>Signup.js</p>
<pre><code class="language-perl">...
  const didMountRef = useRef();

  useEffect(() =&gt; {
    if (didMountRef.current) {
      let _errorMessage = &#39;&#39;;

      if (!name) {
        _errorMessage = &#39;Please enter your name.&#39;;
      } else if (!validateEmail(email)) {
        _errorMessage = &#39;Please verify your email.&#39;;
      } else if (password.length &lt; 6) {
        _errorMessage = &#39;The password must contain 6 characters at least.&#39;;
      } else if (password !== passwordConfirm) {
        _errorMessage = &#39;Passwords need to match.&#39;;
      } else {
        _errorMessage = &#39;&#39;;
      }
      setErrorMessage(_errorMessage);
    } else {
      didMountRef.current = true;
    }
  }, [name, email, password, passwordConfirm]);
...</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-10"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li><code>useRef</code>를 사용하여 컴포넌트 마운트 여부를 추적하는 <code>didMountRef</code> 변수를 추가함</li>
<li><code>useEffect</code> 내부에서 <code>didMountRef.current</code> 값을 확인하여 초기 렌더링 시에는 유효성 검사 로직이 실행되지 않도록 수정함</li>
<li>사용자가 입력을 시작한 이후(update 시점)부터 유효성 검사를 수행하여 초기 화면 진입 시 불필요한 에러 메시지가 노출되는 문제를 해결함</li>
<li>Signup 화면의 패딩 값을 <code>0 20px</code>에서 <code>40px 20px</code>로 수정하여 상하 여백을 확보함</aside>

</li>
</ol>
<p>utils/image.js</p>
<pre><code class="language-perl">photo: `${prefix}/photo.png?alt=media`,</code></pre>
<p>회원가입 이미지를 파이어베이스에서 불러오도록 한다</p>
<p>Signup.js</p>
<pre><code class="language-perl">import { images } from &#39;../utils/images&#39;

...

const Signup = ({ navigation }) =&gt; {
  const [photoUrl, setPhotoUrl] = useState(images.photo)
  ...
  return (
    ...
      &lt;Container&gt;
        &lt;Image rounded url={photoUrl}/&gt;
        ...
      &lt;/Container&gt;
    ...
  );
};</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-11"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li>utils/images에서 정의한 기본 프로필 이미지를 불러오기 위해 images 객체를 import함</li>
<li>프로필 이미지 URL을 관리하기 위한 photoUrl 상태 변수를 선언하고 초기값을 images.photo로 설정함</li>
<li>Image 컴포넌트에 url 속성으로 photoUrl 상태값을 전달하여 화면에 기본 프로필 사진이 표시되도록 구현함</aside>

</li>
</ol>
<p>유저가 직접 갤러리에 있는 이미지를 삽입할 수 있도록 버튼을 만든다.</p>
<p>theme.js</p>
<pre><code class="language-perl">  imageBackground: colors.grey_0,
  imageButtonBackground: colors.grey_1,
  imageButtonIcon: colors.white,</code></pre>
<p>버튼에 들어갈 색상을 정의한다</p>
<p>image.js</p>
<pre><code class="language-perl">import { MaterialIcons } from &#39;@expo/vector-icons&#39;;

...

const ButtonContainer = styled.TouchableOpacity`
  background-color: ${({ theme }) =&gt; theme.imageButtonBackground};
  position: absolute;
  bottom: 0;
  right: 0;
  width: 30px;
  height: 30px;
  border-radius: 15px;
  justify-content: center;
  align-items: center;
`;

const ButtonIcon = styled(MaterialIcons).attrs({
  name: &#39;photo-camera&#39;,
  size: 22,
})`
  color: ${({ theme }) =&gt; theme.imageButtonIcon};
`;

const PhotoButton = ({ onPress }) =&gt; {
  return (
    &lt;ButtonContainer onPress={onPress}&gt;
      &lt;ButtonIcon /&gt;
    &lt;/ButtonContainer&gt;
  );
};

...

const Image = ({ url, imageStyle, rounded, showButton, onPress }) =&gt; {
  return (
    &lt;Container&gt;
      &lt;StyledImage source={{ uri: url }} style={imageStyle} rounded={rounded} /&gt;
      {showButton &amp;&amp; &lt;PhotoButton onPress={onPress} /&gt;}
    &lt;/Container&gt;
  );
};

...</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-12"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li>이미지 우측 하단에 카메라 아이콘 버튼을 표시하기 위해 <code>PhotoButton</code>, <code>ButtonContainer</code>, <code>ButtonIcon</code> 컴포넌트를 구현함</li>
<li><code>Image</code> 컴포넌트의 props로 <code>showButton</code>을 전달받아 <code>true</code>일 경우에만 카메라 버튼이 렌더링되도록 조건부 렌더링을 적용함</li>
<li><code>showButton</code>이 활성화된 경우 사용자가 버튼을 클릭하면 <code>onPress</code> 함수가 실행되도록 연결함</li>
<li>기존 <code>uri</code> prop의 이름을 <code>url</code>로 변경하고, <code>Image</code> 컴포넌트 사용처에서도 이를 반영하도록 수정함</aside>

</li>
</ol>
<p>Signup.js</p>
<pre><code class="language-perl">...
  &lt;Container&gt;
    &lt;Image rounded url={photoUrl} showButton /&gt;
    ...
  &lt;/Container&gt;
...</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-13"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li>회원가입 화면(Signup)에서 <code>Image</code> 컴포넌트에 <code>showButton</code> 속성을 추가함</li>
<li><code>showButton</code> 값을 <code>true</code>로 설정하여 프로필 이미지 우측 하단에 카메라 아이콘 버튼이 보이도록 활성화함</aside>

</li>
</ol>
<p>갤러리에 직접 접근하여 사진을 불러오도록 구현해보자</p>
<pre><code class="language-perl">npm install expo-image-picker</code></pre>
<p>expo-image-picker 설치</p>
<p>Image.js</p>
<pre><code class="language-perl">import { Platform, Alert } from &#39;react-native&#39;;
import * as ImagePicker from &#39;expo-image-picker&#39;;

...

const Image = ({ url, imageStyle, rounded, showButton, onChangeImage }) =&gt; {
  useEffect(() =&gt; {
    (async () =&gt; {
      try {
        if (Platform.OS === &#39;ios&#39;) {
          const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
          if (status !== &#39;granted&#39;) {
            Alert.alert(
              &#39;Photo Permission&#39;,
              &#39;Please turn on the camera roll permissions.&#39;
            );
          }
        }
      } catch (e) {
        Alert.alert(&#39;Photo Permission Error&#39;, e.message);
      }
    })();
  }, []);

  const _handleEditButton = async () =&gt; {
    try {
      const result = await ImagePicker.launchImageLibraryAsync({
        mediaTypes: ImagePicker.MediaTypeOptions.Images,
        allowsEditing: true,
        aspect: [1, 1],
        quality: 1,
      });

      if (!result.canceled) {
        onChangeImage(result.assets[0].uri);
      }
    } catch (e) {
      Alert.alert(&#39;Photo Error&#39;, e.message);
    }
  };

  return (
    &lt;Container&gt;
      &lt;StyledImage source={{ uri: url }} style={imageStyle} rounded={rounded} /&gt;
      {showButton &amp;&amp; &lt;PhotoButton onPress={_handleEditButton} /&gt;}
    &lt;/Container&gt;
  );
};</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-14"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li>useEffect를 통해 컴포넌트 마운트 시, iOS 환경에서는 갤러리 접근 권한을 요청하고 거부될 경우 알림을 띄우도록 처리함</li>
<li>버튼 클릭 핸들러(_handleEditButton) 내부에서 launchImageLibraryAsync를 호출하여 이미지 라이브러리를 실행함</li>
<li>사용자가 이미지를 선택하고 편집(crop)을 마치면 onChangeImage 콜백 함수를 통해 선택된 이미지 URI를 부모 컴포넌트로 전달함</li>
<li>이미지 선택 과정에서 예외가 발생할 경우 Alert을 통해 에러 메시지를 사용자에게 알리도록 함</aside>

</li>
</ol>
<p>Signup.js</p>
<pre><code class="language-perl">...
  &lt;Container&gt;
    &lt;Image
      rounded
      url={photoUrl}
      showButton
      onChangeImage={url =&gt; setPhotoUrl(url)}
    /&gt;
    ...
  &lt;/Container&gt;
...</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-15"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li>Signup 화면의 Image 컴포넌트에 onChangeImage props를 전달함</li>
<li>Image 컴포넌트 내부에서 이미지가 선택되면 호출되는 콜백 함수를 연결함</li>
<li>선택된 이미지의 URL을 받아 setPhotoUrl 상태 업데이트 함수를 실행하여 화면에 즉시 반영되도록 구현함</aside>

</li>
</ol>
<p>이제 파이어베이스에서 정보를 요청하고 전송, 인증받는 코드를 구현해보자</p>
<p>Login.js</p>
<pre><code class="language-perl">import { Alert } from &#39;react-native&#39;;
import { login } from &#39;../utils/firebase&#39;;

...

  const _handleLoginButtonPress = async () =&gt; {
    try {
      const user = await login({ email, password });
      Alert.alert(&#39;Login Success&#39;, user.email);
    } catch (e) {
      Alert.alert(&#39;Login Error&#39;, e.message);
    }
  };

...

        &lt;Button title=&quot;Login&quot; onPress={_handleLoginButtonPress} disabled={disabled} /&gt;</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-16"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li>Firebase의 login 함수를 호출하여 이메일과 비밀번호로 로그인을 시도하는 비동기 함수 <code>_handleLoginButtonPress</code>를 구현함</li>
<li>로그인 성공 시 사용자 이메일을 포함한 성공 알림창을 띄우고, 실패 시 에러 메시지 알림창을 표시함</li>
<li>Login 버튼의 onPress 이벤트에 해당 로그인 핸들러 함수를 연결함</li>
<li>입력값 유효성에 따라 버튼 활성화 상태를 제어하는 <code>disabled</code> 속성을 Login 버튼에 적용함</aside>

</li>
</ol>
<p>Signup.js</p>
<pre><code class="language-perl">import { Alert } from &#39;react-native&#39;;
import { signup } from &#39;../utils/firebase&#39;;

...

  const _handleSignupButtonPress = async () =&gt; {
    try {
      if (disabled) return;

      const trimmedName = name.trim();
      const trimmedEmail = removeWhitespace(email);
      const trimmedPassword = removeWhitespace(password);

      // (중략: 유효성 검사 로직)

      const user = await signup({
        email: trimmedEmail,
        password: trimmedPassword,
        name: trimmedName,
        photoUrl,
      });

      Alert.alert(&#39;Signup Success&#39;, user?.email ?? trimmedEmail);
      navigation.navigate(&#39;Login&#39;);
    } catch (e) {
      Alert.alert(&#39;Signup Error&#39;, e?.message ?? &#39;Unknown error&#39;);
    }
  };

...</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-17"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li>Firebase의 <code>signup</code> 함수를 호출하여 회원가입 정보를 전송하는 비동기 함수 <code>_handleSignupButtonPress</code>를 구현함</li>
<li>회원가입 버튼 클릭 시 이름, 이메일, 비밀번호의 공백을 제거하고 최종 유효성 검사를 수행함</li>
<li>회원가입 성공 시 성공 알림창을 띄우고 로그인 화면(<code>Login</code>)으로 이동하도록 내비게이션 처리를 추가함</li>
<li>실패 시에는 <code>catch</code> 블록에서 에러 메시지를 알림창으로 표시하여 사용자에게 안내함</aside>

</li>
</ol>
<p>Signup.js</p>
<pre><code class="language-perl">const _handleSignupButtonPress = async () =&gt; {
    try {
      const trimmedName = name.trim();
      const trimmedEmail = removeWhitespace(email);
      const trimmedPassword = removeWhitespace(password);
      const trimmedPasswordConfirm = removeWhitespace(passwordConfirm);

      const user = await signup({
        email: trimmedEmail,
        password: trimmedPassword,
        name: trimmedName,
        photoUrl,
      });

      console.log(user);
      Alert.alert(&#39;Signup Success&#39;, user.email);
    } catch (e) {
      Alert.alert(&#39;Signup Error&#39;, e.message);
    }
  };</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-18"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li>기존에 함수 내부에서 수행하던 중복된 유효성 검사 로직(if문들)을 제거하고 코드를 간소화함</li>
<li>이미 <code>useEffect</code>에서 <code>disabled</code> 상태를 관리하고 있으므로, 버튼 클릭 시에는 별도의 중복 검사 없이 바로 회원가입 요청을 보내도록 수정함</li>
<li>회원가입 성공 시 콘솔에 사용자 정보를 출력하는 디버깅용 코드를 추가하고, 알림창 메시지를 간결하게 변경함</aside>

</li>
</ol>
<p>로그인과 회원가입이 완전히 진행 되기 전 다른 버튼이 눌리지 않도록 대기시키는 스피너를 구현해보자</p>
<p>theme.js</p>
<pre><code class="language-perl">  spinnerBackground: colors.black,
  spinnerIndicator: colors.white,</code></pre>
<p>색상을 정의한다</p>
<p>Spinner.js</p>
<pre><code class="language-perl">import React, { useContext } from &#39;react&#39;;
import { ActivityIndicator } from &#39;react-native&#39;;
import styled, { ThemeContext } from &#39;styled-components/native&#39;;

const Container = styled.View`
  position: absolute;
  z-index: 2;
  opacity: 0.3;
  width: 100%;
  height: 100%;
  justify-content: center;
  background-color: ${({ theme }) =&gt; theme.spinnerBackground};
`;

const Spinner = () =&gt; {
  const theme = useContext(ThemeContext);

  return (
    &lt;Container&gt;
      &lt;ActivityIndicator size=&quot;large&quot; color={theme.spinnerIndicator} /&gt;
    &lt;/Container&gt;
  );
};

export default Spinner;</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-19"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li>로딩 상태를 표시하기 위한 재사용 가능한 Spinner 컴포넌트를 새로 생성함</li>
<li><code>ActivityIndicator</code>를 사용하여 기본 로딩 인디케이터를 화면 중앙에 배치함</li>
<li><code>styled-components</code>를 활용해 화면 전체를 덮는 절대 위치(<code>position: absolute</code>)의 반투명 배경 컨테이너를 구현함</li>
<li><code>z-index</code>를 2로 설정하여 다른 UI 요소들보다 위에 나타나도록 처리함</li>
<li><code>useContext</code>로 현재 테마 정보를 가져와 인디케이터 색상(<code>color</code>)을 동적으로 설정함</aside>

</li>
</ol>
<p>Spinner.js 를 components의 index와 navigations의 index에 등록한다</p>
<p>contexts/Progress.js</p>
<pre><code class="language-perl">import React, { useState, createContext } from &#39;react&#39;;

const ProgressContext = createContext({
  inProgress: false,
  spinner: () =&gt; {},
});

const ProgressProvider = ({ children }) =&gt; {
  const [inProgress, setInProgress] = useState(false);

  const spinner = {
    start: () =&gt; setInProgress(true),
    stop: () =&gt; setInProgress(false),
  };

  const value = { inProgress, spinner };

  return (
    &lt;ProgressContext.Provider value={value}&gt;
      {children}
    &lt;/ProgressContext.Provider&gt;
  );
};

export { ProgressContext, ProgressProvider };</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-20"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li>앱 전역에서 로딩 상태(스피너)를 관리하기 위해 Context API를 활용한 ProgressContext와 ProgressProvider를 구현함</li>
<li>useState를 사용하여 로딩 중인지 여부를 나타내는 inProgress 상태 변수를 생성함</li>
<li>로딩 상태를 제어하기 위한 spinner 객체를 정의하고, start(로딩 시작)와 stop(로딩 종료) 함수를 구현함</li>
<li>Provider를 통해 하위 컴포넌트들이 inProgress 상태와 spinner 제어 함수에 접근할 수 있도록 value를 전달함</aside>

</li>
</ol>
<p>contexts/index.js</p>
<pre><code class="language-perl">import { ProgressContext, ProgressProvider } from &#39;./Progress&#39;;

export { ProgressContext, ProgressProvider };</code></pre>
<p>contexts/index.js에 Progress.js를 등록해준다. 파일을 더 쉽게 관리하기 위함이다.</p>
<p>App.js</p>
<pre><code class="language-perl">import { ProgressProvider } from &#39;./contexts&#39;;

...

const App = () =&gt; {
  ...
  return isReady ? (
    &lt;ThemeProvider theme={theme}&gt;
      &lt;ProgressProvider&gt;
        &lt;StatusBar barStyle=&quot;dark-content&quot; /&gt;
        &lt;Navigation /&gt;
      &lt;/ProgressProvider&gt;
    &lt;/ThemeProvider&gt;
  ) : (
    ...
  );
};</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-21"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li>Navigation 컴포넌트를 ProgressProvider로 감싸 앱 전역에서 로딩 상태(스피너)를 사용할 수 있도록 설정함</li>
<li>이를 통해 하위 컴포넌트(로그인, 회원가입 등)에서 Context를 통해 스피너를 제어할 수 있게 됨</aside>

</li>
</ol>
<p>Login.js</p>
<pre><code class="language-perl">import React, { useState, useRef, useEffect, useContext } from &#39;react&#39;;
import { ProgressContext } from &#39;../contexts&#39;;

...

const Login = ({ navigation }) =&gt; {
  const { spinner } = useContext(ProgressContext);

  ...

  const _handleLoginButtonPress = async () =&gt; {
    try {
      spinner?.start?.();
      const user = await login({ email, password });
      Alert.alert(&#39;Login Success&#39;, user.email);
    } catch (e) {
      Alert.alert(&#39;Login Error&#39;, e.message);
    } finally {
      spinner?.stop?.();
    }
  };

  ...
};</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-22"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li>로그인 버튼 핸들러(<code>_handleLoginButtonPress</code>)의 시작 부분에서 <code>spinner.start()</code>를 호출하여 로딩 인디케이터를 화면에 표시함</li>
<li>비동기 로그인 작업(<code>await login</code>)이 완료되거나 에러가 발생하면, <code>finally</code> 블록에서 <code>spinner.stop()</code>을 호출하여 스피너를 숨김</li>
<li>이를 통해 사용자가 로그인 요청 중임을 시각적으로 인지할 수 있도록 UX를 개선함</aside>

</li>
</ol>
<p>Signup.js</p>
<pre><code class="language-perl">import React, { useState, useRef, useEffect, useContext } from &#39;react&#39;;
import { ProgressContext } from &#39;../contexts&#39;;

...

const Signup = ({ navigation }) =&gt; {
  const { spinner } = useContext(ProgressContext);

  ...

  const _handleSignupButtonPress = async () =&gt; {
    try {
      spinner.start();

      const trimmedName = name.trim();
      const trimmedEmail = removeWhitespace(email);
      const trimmedPassword = removeWhitespace(password);

      ...

      Alert.alert(&#39;Signup Success&#39;, user.email);
    } catch (e) {
      Alert.alert(&#39;Signup Error&#39;, e.message);
    } finally {
      spinner.stop();
    }
  };

  ...
};</code></pre>
<aside>

<h3 id="📝-수정된-동작-요약-23"><strong>📝 수정된 동작 요약</strong></h3>
<ol>
<li><code>spinner</code> 객체를 Context로부터 구조 분해 할당으로 받아옴</li>
<li>회원가입 버튼 핸들러(<code>_handleSignupButtonPress</code>) 시작 부분에 <code>spinner.start()</code>를 호출하여 로딩 화면을 표시함</li>
<li>비동기 작업 종료 시(<code>finally</code> 블록) <code>spinner.stop()</code>을 호출하여 스피너를 제거함으로써, 회원가입 진행 중임을 사용자에게 시각적으로 알리는 로직을 완성함</aside>



</li>
</ol>
<p>현재 파이어베이스에서 문제가 생겨 무한 로딩이 된다... 책과 달라진 부분이 많아 찾으면서 오류를 고치고 있는 중이다. 다 고쳐지면 글 수정해야지 ㅜ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[처음 배우는 리액트 네이티브] 채팅 어플리케이션 만들기 - 1. 파이어베이스 설정]]></title>
            <link>https://velog.io/@choi-ss-choco/%EC%B2%98%EC%9D%8C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C-%EC%B1%84%ED%8C%85-%EC%96%B4%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%A7%8C%EB%93%A4%EA%B8%B0-1.-%ED%8C%8C%EC%9D%B4%EC%96%B4%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@choi-ss-choco/%EC%B2%98%EC%9D%8C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C-%EC%B1%84%ED%8C%85-%EC%96%B4%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%A7%8C%EB%93%A4%EA%B8%B0-1.-%ED%8C%8C%EC%9D%B4%EC%96%B4%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Wed, 10 Dec 2025 03:14:00 GMT</pubDate>
            <description><![CDATA[<p>채팅 어플리케이션을 만들기 위해 서버를 구현하지 않고 파이어베이스를 이용할 예정이다.</p>
<h2 id="프로젝트-생성">프로젝트 생성</h2>
<p><img src="https://velog.velcdn.com/images/choi-ss-choco/post/7d394a2d-cc37-490e-9295-ddd5d20fcf5d/image.png" alt="">
새 프로젝트 만들기를 선택한다. </p>
<p><img src="https://velog.velcdn.com/images/choi-ss-choco/post/2dfccf8e-dc2a-464f-bd16-8f6c5fcd246c/image.png" alt="">
가운데 앱 추가 버튼을 눌러 웹을 선택한다.</p>
<blockquote>
<p>책과 달라진 부분이 있어 아래 블로그를 참고하였다.
<a href="https://gamza1013.tistory.com/30">https://gamza1013.tistory.com/30</a></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/choi-ss-choco/post/54532fff-c875-44a5-b05d-78e900325cbc/image.png" alt="">
적당한 닉네입을 입력 후 앱 등록을 한다.
SDK 설정 및 등록에서 npm 방식을 이용한다.</p>
<pre><code class="language-Shell">npm install --save @react-native-firebase/app
npm install @react-native-firebase/database
npm install @react-native-firebase/firestore</code></pre>
<p>firebase를 설치해준다.</p>
<p>이후 프로젝트 src/utils에 firebase.js 파일을 생성하고 SDK 설정 및 구성에 뜬 코드를 붙여넣어준다. 외부에 유출되지 않도록 조심한다.</p>
<h2 id="authentication">Authentication</h2>
<p><img src="https://velog.velcdn.com/images/choi-ss-choco/post/7bbe73d8-a78c-4510-b850-6ab5baa63219/image.png" alt="">
로그인 구현을 위해 인증 탭으로 이동한다. 형식은 이메일-비밀번호를 사용할 예정이다.
시작하기를 누르고 이메일-비밀번호 사용을 활성화해준다.</p>
<h2 id="firestore-database">Firestore Database</h2>
<p>앱에서 생성되는 데이터를 관리하기 위해 데이터베이스를 사용해야 한다. 이 프로젝트에서는 파이어베이스에서 제공하는 파이어스토어를 이용할 예정이다.</p>
<p><img src="https://velog.velcdn.com/images/choi-ss-choco/post/83bf2a71-75db-4807-9044-a32e66916584/image.png" alt="">
왼쪽 메뉴에서 빌드&gt;Firestore Database를 선택한다</p>
<p><img src="https://velog.velcdn.com/images/choi-ss-choco/post/4deaf3e5-c074-4fa7-b368-2043f9719eee/image.png" alt="">
개인 프로젝트이므로 Standard버전을 사용했다.<img src="https://velog.velcdn.com/images/choi-ss-choco/post/cce85afc-65b3-4bad-8459-5a721ca8dabc/image.png" alt="">
자신의 지역과 가까운 곳을 선택하는 것이 좋다고 한다.
<img src="https://velog.velcdn.com/images/choi-ss-choco/post/61fa389b-1242-47e1-bfec-00345fe6fdfa/image.png" alt="">
위와 같이 선택하고 만들기를 눌러주었다.</p>
<h2 id="storage">Storage</h2>
<p>가입한 사용자의 사진을 저장하고 불러오는 기능을 구현하기 위해 스토리지를 사용해야 한다.
<img src="https://velog.velcdn.com/images/choi-ss-choco/post/b5c927cc-6c11-45ab-93a5-c1f28edb1e08/image.png" alt="">
왼쪽 메뉴바에서 스토리지를 선택한다.
<img src="https://velog.velcdn.com/images/choi-ss-choco/post/0acef388-25a4-4570-ad09-ff20c1a71b3b/image.png" alt="">
돈을 내야 한다고 한다... 무료 크레딧을 받을 수 있다고 하여 시도했다.
<del><em>(찾아보니 1GB까지는 무료로 사용할 수 있다고 한다. 초과 사용 내역에 대해서 요금이 청구되어 결제 수단을 등록해야 한다. 아래처럼 무료 크레딧을 받지 않더라도 요금 지불 없이 사용할 수 있는 것이다! 미리 알았다면 하지 않았을 것이다. 혹시 모를 추가 요금에 대비하여 미리 받아둘지 말지는 자신의 선택 사항일 것 같다)</em></del>
<img src="https://velog.velcdn.com/images/choi-ss-choco/post/95423ad0-0f3e-47b0-b9c3-41acd8251e19/image.png" alt="">
자세히 알아보기 클릭
<img src="https://velog.velcdn.com/images/choi-ss-choco/post/7c777f27-0d59-442e-89a8-d4ecb5e19cd6/image.png" alt="">
Check if you’re eligible for the Free Trial. 클릭
<img src="https://velog.velcdn.com/images/choi-ss-choco/post/5d624d62-236a-4125-b2fa-a560283340d2/image.png" alt="">
결제 프로필을 등록하라고 해서 봤더니 내가 선택하지 않는 이상 자동으로 요금 청구는 되지 않는다고 명시되어있다. 아래 주소와 번호 등의 개인정보를 입력했다.
다음으로 넘겨 카드 정보도 등록하였다.
<img src="https://velog.velcdn.com/images/choi-ss-choco/post/cc296ddf-6383-44d7-a47d-d1495afe40bf/image.png" alt="">
90일 무료 크레딧을 얻었다ㅜㅜ</p>
<p><img src="https://velog.velcdn.com/images/choi-ss-choco/post/86b649b7-a6f4-4ae2-8478-10a5e319dac2/image.png" alt="">
내 결제 계정을 선택한다. <del><em>(저 평가판 결제 계정이 뭐지 했는데 예전에 동아리에서 크레딧을 준다 하여 신청했던... 무언가인 것 같다. 5달러의 크레딧이 있다. 그냥 저거 쓸 걸!)</em></del>
<img src="https://velog.velcdn.com/images/choi-ss-choco/post/f12c89c8-d6fc-44b3-9339-265d2182aad5/image.png" alt=""></p>
<p>무료 크레딧을 300달러 받았으니 예산금액으로 300을 적어준다. 일정 수준으로 사용하면 메일을 준다고 한다. 내가 받은 300 크레딧이 이 결제 계정에 등록되어있는 것인지 궁금하다면 계정보기&gt;크레딧을 확인해본다.</p>
<p><img src="https://velog.velcdn.com/images/choi-ss-choco/post/eaf7addb-5845-4c2e-b2ec-44d9dc1e724d/image.png" alt="">
이후 프로덕션 모드를 선택하고 스토리지를 생성했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스 - CHAPTER 2. 관계 데이터 모델 (2)]]></title>
            <link>https://velog.io/@choi-ss-choco/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-CHAPTER-2.-%EA%B4%80%EA%B3%84-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%AA%A8%EB%8D%B8-2</link>
            <guid>https://velog.io/@choi-ss-choco/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-CHAPTER-2.-%EA%B4%80%EA%B3%84-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%AA%A8%EB%8D%B8-2</guid>
            <pubDate>Sun, 07 Dec 2025 09:28:54 GMT</pubDate>
            <description><![CDATA[<h2 id="무결성-제약조건">무결성 제약조건</h2>
<p>데이터 무결성(integrity)은 데이터베이스에 저장된 데이터의 일관성과 정확성을 지키는 것을 말한다. </p>
<h4 id="도메인-무결성-제약조건">도메인 무결성 제약조건</h4>
<p>도메인 제약(domain constraint)라고도 하며 릴레이션 내의 투플들이 각 속성의 도메인에 지정된 값만을 가져야 한다는 조건이다. 속성값과 관련된 무결성으로 SQL문에서 데이터 형식(type), null, default, chek 등을 사용하여 지정할 수 있다. 일반적으로 프로그래밍 언어의 변수형 선언과 개념이 유사하다.</p>
<h4 id="개체-무결성-제약조건">개체 무결성 제약조건</h4>
<p>기본키 제약(primary key constraint)라고도 한다. 릴레이션은 기본키를 지정하고 그에 따른 무결성 원칙, 즉 기본키는 NULL값을 가져서는 안 되며 릴레이션 내에 오직 하나의 값만 존재해야 한다는 조건이다.</p>
<ul>
<li>삽입: 기본키 값이 같으면 삽입이 거부됨</li>
<li>수정: 기본키와 값이 같거나 NULL값이면 수정이 거부됨</li>
<li>삭제: 특별한 확인 없이 즉시 수행</li>
</ul>
<h4 id="참조-무결성-제약-조건">참조 무결성 제약 조건</h4>
<p>외래키 제약(foriegn key constraint)이라고도 한다. 릴레이션 간의 참조 관계를 선언하는 제약 조건이다. 일반적으로 참조되는 릴레이션을 부모(parent)릴레이션, 참조하는 릴레이션을 자식(child)릴레이션이라고 한다. 부모 릴레이션의 도메인과 다른 값으로 삽입/수정되거나 반대로 자식 릴레이션에서 참조하고 있는 값을 부모 릴레이션에서 삭제하거나 다른 값으로 변경할 경우 거부된다. 자식 릴레이션의 외래키는 부모 릴레이션의 기본키와 도메인이 같아야 한다. </p>
<ul>
<li><p>삽입</p>
<p><strong>자식 릴레이션에서</strong></p>
<ol>
<li>도메인 무결성 제약조건, 개체 무결성 제약조건을 먼저 확인한다.</li>
<li>부모 릴레이션의 기본키에 존재하는지 확인한다.</li>
</ol>
<p><strong>부모 릴레이션에서</strong>
개체 무결성 제약 조건에 따르면 된다.</p>
<ul>
<li>삭제</li>
</ul>
<p><strong>자식 릴레이션에서</strong>
바로 삭제 가능하다.</p>
<p><strong>부모 릴레이션에서</strong>
자식 릴레이션이 참조하고 있는 경우 즉시 삭제가 불가능하며 다음 중 하나의 조치를 따른다.</p>
<ol>
<li><p>즉시 작업 중지</p>
</li>
<li><p>자식 릴레이션의 관련 튜플을 삭제</p>
</li>
<li><p>초기에 설정된 다른 어떤 값으로 변경</p>
</li>
<li><p>NULL값으로 설정</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/3b1ce795-057e-400e-acb4-ee9c00fa9a2a/image.png" alt=""></p>
</li>
</ol>
<ul>
<li>수정
수정은 삭제와 삽입의 명령이 연속해서 수행된다고 보면 된다.</li>
</ul>
<p>다음 표는 제약조건을 정리한 것이다.
<img src="https://images.velog.io/images/choi-ss-choco/post/7a90375f-276f-4c19-b99a-bb72801cbf4a/image.png" alt=""></p>
<h2 id="관계대수">관계대수</h2>
<h4 id="관계대수-relational-algebra">관계대수 (relational algebra)</h4>
<p>릴레이션에서 원하는 결과를 얻기 위해 수학의 대수와 같은 연산을 이용하여 질의하는 방법을 기술하는 언어이다. 절차적 언어이다.</p>
<h4 id="관계대수-연산자operator">관계대수 연산자(operator)</h4>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/a30bfe54-64b8-4fba-85c2-f8bd8d6eb06c/image.png" alt=""></p>
<h4 id="관계대수식relational-algebra-expression">관계대수식(relational algebra expression)</h4>
<p>관계대수 연산을 수행하기 위한 식. 관계대수식은 대상이 되는 릴레이션과 연산자로 구성이 된다. 수학의 연산과 동일하게 괄호 안의 식이 우선하며 왼쪽에서 오른쪽으로 진행된다. </p>
<ul>
<li><p>셀렉션 (selection)
릴레이션의 튜플을 추출하기 위한 연산. 찾고자 하는 튜플의 조건을 명시하고 그 조건에 만족하는 튜플을 반환한다. 결과 릴레이션의 차수는 대상 릴레이션과 작거나 같다.</p>
</li>
<li><p>프로젝션 (projection)
릴레이션의 속성을 추출하기 위한 연산. 결과 릴레이션의 차수는 대상 릴레이션의 차수보다 작거나 같고 카디날리티는 동일하다. </p>
</li>
<li><p>집합연산</p>
</li>
</ul>
<p>집합연산은 두 개의 릴레이션을 대상으로 하는 이항 연산자이다. 카티션 프로덕트를 제외한 나머지 세 개의 연산에서는 두 릴레이션간 차수 및 도메인의 속성과 순서가 동일해야 한다. 이 조건을 합병가능(union compatible)이라고 한다. 속성 이름은 동일하지 않아도 되며 반환되는 릴레이션의 속성 이름은 첫 번째 릴레이션의 이름과 같다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[처음부터 배우는 리액트 네이티브] 네비게이션]]></title>
            <link>https://velog.io/@choi-ss-choco/%EC%B2%98%EC%9D%8C%EB%B6%80%ED%84%B0-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C-%EB%84%A4%EB%B9%84%EA%B2%8C%EC%9D%B4%EC%85%98</link>
            <guid>https://velog.io/@choi-ss-choco/%EC%B2%98%EC%9D%8C%EB%B6%80%ED%84%B0-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C-%EB%84%A4%EB%B9%84%EA%B2%8C%EC%9D%B4%EC%85%98</guid>
            <pubDate>Sun, 23 Nov 2025 08:40:12 GMT</pubDate>
            <description><![CDATA[<h1 id="내비게이션">내비게이션</h1>
<h2 id="리액트-내비게이션">리액트 내비게이션</h2>
<p>지원하는 내비게이션의 종류는 스택 내비게이션, 탭 내비게이션, 드로어 내비게이션이다.</p>
<p><em>책에서는 내비게이션 5버전으로 실습을 하는데, 찾아보니 현재 버전은 7이다!</em></p>
<p><img src="https://velog.velcdn.com/images/choi-ss-choco/post/f311fea7-5d2d-4478-8952-4d125705a7eb/image.png" alt=""></p>
<p>Screen 컴포넌트는 화면으로 사용되는 컴포넌트로 name(화면 이름)과 component(화면으로 사용될 컴포넌트) 속성을 지정해야 한다. 화면으로 사용되는 컴포넌트에는 항상 navigation과 route가 props로 전달된다.</p>
<p>Navigator컴포넌트는 화면을 관리하는 중간 관리자 역할이다.</p>
<p>NavigationContainer컴포넌트는 내비게이션 계층 구조와 상태를 관리하는 컨테이너 역할을 한다. 모든 내비게이션 구성요소를 감싼 최상위 컴포넌트여야 한다.</p>
<p>부모의 설정은 자식에게도 적용되므로, 공통적으로 적용하고 싶은 속성의 경우에는 Navigator, 각 화면에 적용하고 싶은 속성은 Screen을 이용한다. 작은 범위의 설정일수록 우선순위가 높다.</p>
<p>폴더 구조는 다음과 같이 한다.</p>
<pre><code class="language-jsx">react-native-navigation
└── src
    ├── navigations
    ├── screens
    │── App.js
└── App.js</code></pre>
<p>리액트 내비게이션 라이브러리를 설치한다.</p>
<pre><code class="language-jsx">npm install --save @react-navigation/native</code></pre>
<p>필요한 종속성을 설치한다.</p>
<pre><code class="language-jsx">expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view</code></pre>
<h2 id="스택-내비게이션">스택 내비게이션</h2>
<p>스택 네비게이션에 필요한 라이브러리를 설치한다.</p>
<pre><code class="language-jsx">npm install greact-navigation/stack</code></pre>
<p>스택 네비게이션은 일반적으로 가장 많이 사용되며 현재 화면 위에 다른 화면을 쌓으며 이동한다. </p>
<pre><code class="language-jsx">import React from &#39;react&#39;;
import { createStackNavigator } 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; component={Home} /&gt;
      &lt;Stack.Screen name=&quot;List&quot; component={List} /&gt;
      &lt;Stack.Screen name=&quot;Item&quot; component={Item} /&gt;
    &lt;/Stack.Navigator&gt;
  );
};

export default StackNavigation;</code></pre>
<p>가장 먼저 Home 화면이 뜨고, 다른 화면으로 이동할 수 있으며, 뒤로가기 하면 이전 화면으로 돌아간다. 첫번째로 나오는 화면은 Navigator의 첫번째 자식이며 순서를 바꾸면 다른 화면이 첫 화면으로 나온다. </p>
<pre><code class="language-jsx">‹Stack.Navigator initialRouteName=&quot; Home&quot;&gt;</code></pre>
<p>혹은 initialRouteName 속성을 이용해 첫번째 화면을 지정한다.</p>
<pre><code class="language-jsx">const Home = ({ navigation }) =&gt; {
  return (
    &lt;Container&gt;
      &lt;StyledText&gt;Home&lt;/StyledText&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>navigation함수를 이용해서 원하는 화면의 이름을 전달하면 해당 화면으로 이동한다. 전달되는 화면의 이름은 Screen컴포넌트의 name값 중 하나를 입력해야 한다.</p>
<pre><code class="language-jsx">//List.js
const List = ({ navigation }) =&gt; {
  const _onPress = item =&gt; {
    navigation.navigate(&#39;Detail&#39;, { id: item._id, name: item.name });
  };</code></pre>
<pre><code class="language-jsx">//Item.js
const Item = ({ route }) =&gt; {
  return (
      &lt;Container&gt;
        &lt;StyledText&gt;Item&lt;/StyledText&gt;
        &lt;StyledText&gt;ID: {route.params,id}&lt;/StyledText&gt;
        &lt;StyledText&gt;Name: {route.params,name}&lt;/StyledText&gt;
    &lt;/Container&gt;
  );
 };</code></pre>
<p>List에서 상세 Item화면으로 이동할 때, id와 name을 함께 전달한다. 전달된 내용은 컴포넌트의 props로 전달되는 route의 params를 통해 확인할 수 있다.</p>
<pre><code class="language-jsx">&lt;Stack Navigator
    initialRouteName=&quot;Home&quot;
    screenOptions={{cardStyle: { backgroundColor: &#39;#ffffff&#39; } }}
    &gt;</code></pre>
<p>navigation이 화면 전체를 차지하고 있지 않아 내부는 스타일이 적용 안 될 수도 있다. 그 땐 cardStyle을 이용한다.</p>
<p>헤더의 타이틀은 Screen의 name속성을 기본값으로 한다. 하지만 name 속성을 이용하는 곳을 모두 찾아다니며 수정하는 것은 번거롭다.</p>
<pre><code class="language-jsx">&lt;Stack.Screen
    name=&quot;List&quot;
    component={List}
    options={{headerTitle: &#39;List Screen&#39; }}
/&gt;</code></pre>
<p>headerTitle속성을 이용하면 name을 바꿀 필요 없이 헤더의 타이틀이 변경된다.</p>
<pre><code class="language-jsx">headerStyle: {
height: 110,
backgroundColor: &#39;#95a5a6&#39;,
borderBottomWidth: 5,
borderBottomColor: &#39;#34495e&#39;,
},
headerTitleStyle: { color: &quot;#ffffff&#39;, fontSize: 24},
headerTitleAlign: &#39;center&#39;</code></pre>
<p>헤더의 스타일 변경은 headerStyle, headerTitleStyle, 정렬에는 headerTitleAlign 을 이용한다. headerTitleAlign은 center, left 두가지 만 값으로 설정할 수 있으며 ios는 center, andoird는 left가 기본 값이다.</p>
<pre><code class="language-jsx">headerTitle: ({ style )) =&gt; (
    &lt;MaterialCommunityIcons name=&quot;react&quot; style={style} /&gt;
),</code></pre>
<p>타이틀에 문자열이 아닌 다른 것을 이용하고 싶다면 headerTitle을 사용한다</p>
<p>이전 화면으로 이동할 때, 헤더 왼쪽에 뒤로가기 버튼이 생성되는데 ios는 이전 화면의 타이틀을 버튼의 타이틀로 보여주고, android는 그러지 않는다는 차이점이 있다. </p>
<pre><code class="language-jsx">options= {{
headerTitle: &quot;List Screen&#39;,
headerBackTitleVisible: true,
}}</code></pre>
<p>headerBackTitleVisible를 사용하여 버튼 타이틀 렌더링 유무를 동일하게 설정할 수 있다.</p>
<p>헤더를 숨길 때엔 headerMode나 headerShown을 이용한다.  headerMode에는 다음 중 헤더 렌더링 방식을 선택할 수 있다 </p>
<ul>
<li>float → iOS 스타일, 하나의 헤더 유지</li>
<li>screen → Android 스타일, 화면별 헤더 존재</li>
<li>none → 헤더 완전 제거</li>
</ul>
<p>headerShown은 특정 화면 또는 전체 Navigator에서 헤더 표시 여부를 제어한다. 보통 첫 화면(Home)에서만 헤더 숨기기 등에서 headerShown을 활용한다.</p>
<h2 id="탭-내비게이션">탭 내비게이션</h2>
<p>보통 화면 위나 아래에 위치하여 탭 버튼을 누르면 연결된 화면으로 이동하는 방식이다. </p>
<pre><code class="language-jsx">npm install @react-navigation/bottom-tabs</code></pre>
<p>라이브러리를 설치한다. </p>
<pre><code class="language-jsx">//screens/TabScreens.js
import React from &#39;react&#39;;
import styled from &#39;styled-components/native&#39;;

const Container = styled.View`
  flex: 1;
  justify-content: center;
  align-items: center;
  background-color: #54b7f9;
`;
const StyledText = styled.Text`
  font-size: 30px;
`;

export const Mail = () =&gt; {
  return (
    &lt;Container&gt;
      &lt;StyledText&gt;Mail&lt;/StyledText&gt;
    &lt;/Container&gt;
  );
};

export const Meet = () =&gt; {
  return (
    &lt;Container&gt;
      &lt;StyledText&gt;Meet&lt;/StyledText&gt;
    &lt;/Container&gt;
  );
};

export const Settings = () =&gt; {
  return (
    &lt;Container&gt;
      &lt;StyledText&gt;Settings&lt;/StyledText&gt;
    &lt;/Container&gt;
  );
};</code></pre>
<pre><code class="language-jsx">//navigations/Tab.js
import React from &#39;react&#39;;
import { createBottomTabNavigator } from &#39;@react-navigation/bottom-tabs&#39;;
import { Mail, Meet, Settings } 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;
    &lt;/Tab.Navigator&gt;
  );
};

export default TabNavigation;</code></pre>
<p>스택 네비게이션과 동일하게 처음 보여지는 화면은 첫번째 자식이며 initialRouteName을 사용하여 첫 화면을 지정할 수도 있다. </p>
<pre><code class="language-jsx">const TabIcon = ({ name, size, color }) =&gt; {
  return &lt;MaterialCommunityIcons name={name} size={size} color={color} /&gt;;
};

...

&lt;Tab.Screen
  name=&quot;Mail&quot;
  component={Mail}
  options={{
    tabBarIcon: props =&gt;
      TabIcon({ ...props, name: &#39;email&#39; }),
  }}
/&gt;</code></pre>
<p>tabBarIcon을 이용하여 탭바에 아이콘을 삽입할 수 있다.</p>
<pre><code class="language-jsx">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&#39;) name = &#39;video&#39;;
          else name = &#39;settings&#39;;
          return TabIcon({ ...props, name });
        },
      })}
    &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;
    &lt;/Tab.Navigator&gt;
  );
};</code></pre>
<p>각각 지정하지 않고 screenOptions을 이용해 한 곳에서 관리할 수도 있다. </p>
<pre><code class="language-jsx">&lt;Tab.Screen
  name=&quot;Mail&quot;
  component={Mail}
  options={{
    tabBarLabel: &#39;Inbox&#39;,</code></pre>
<p>Screen에서 tabBarLabel값을 설정하여 탭 버튼의 라벨을 변경할 수 있다.</p>
<pre><code class="language-jsx">&lt;Tab.Navigator
  initialRouteName=&quot;Settings&quot;
  tabBarOptions={{
    labelPosition: &#39;beside-icon&#39;,</code></pre>
<p>라벨을 아이콘 옆에 위치시키고 싶다면 labelPosition을 beside-icon으로 설정한다. showlabel을 false로 설정하면 라벨이 보이지 않는다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[처음 배우는 리액트 네이티브] Context API]]></title>
            <link>https://velog.io/@choi-ss-choco/%EC%B2%98%EC%9D%8C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C-Context-API</link>
            <guid>https://velog.io/@choi-ss-choco/%EC%B2%98%EC%9D%8C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C-Context-API</guid>
            <pubDate>Sat, 22 Nov 2025 05:04:28 GMT</pubDate>
            <description><![CDATA[<h1 id="context-api">Context API</h1>
<h2 id="전역-상태-관리">전역 상태 관리</h2>
<p>부모에게서 데이터를 전달 받을 때, 멀리 떨어져있어 여러 컴포넌트를 거쳐야한다면 상당히 번거롭다. 이 때 Context API를 사용하여 Context를 생성해 필요한 컴포넌트에서 데이터를 바로 받아올 수 있다.</p>
<h2 id="context-api-1">Context API</h2>
<p>Context를 생성하는 createContext함는 파라미터에 생성되는 Context의 기본값을 지정할 수 있다.</p>
<h3 id="consumer">Consumer</h3>
<p>생성된 Context 오브젝트는 입력된 기본값 외에도 Consumer컴포넌트와 Provider컴포넌트를 갖고 있다. Consumer 컴포넌트는 상위 컴포넌트 중 가장 가까운 곳에 있는 Provider컴포넌트가 전달하는 데이터를 이용한다. 상위에 Provider가 없다면 기본값을 사용한다. </p>
<p>Consumer 컴포넌트의 자식은 반드시 리액트 컴포넌트를 반환하는 함수여야 하고, Context의 현재값을 파라미터로 전달받아 사용할 수 있다.</p>
<pre><code class="language-jsx">const User = () =&gt; {
  return (
    &lt;UserContext.Provider value={{ name: &#39;React Native&#39; }}&gt;
      &lt;UserContext.Consumer&gt;
        {value =&gt; &lt;StyledText&gt;Name: {value.name}&lt;/StyledText&gt;}
      &lt;/UserContext.Consumer&gt;
    &lt;/UserContext.Provider&gt;
  );
};</code></pre>
<h3 id="provider">Provider</h3>
<p>Context에 있는 Provider컴포넌트른 하위 컴포넌트에 변화를 알리는 역할을 한다. 하위 컴포넌트는 Provider의 값이 변경될때마다 렌더링을 다시 한다.</p>
<pre><code class="language-jsx">const App = () =&gt; {
  return (
    &lt;UserContext.Provider value={{ name: &#39;Dayeon&#39; }}&gt;
      &lt;Container&gt;
        &lt;User /&gt;
      &lt;/Container&gt;
    &lt;/UserContext.Provider&gt;
  );
};</code></pre>
<p>User 컴포넌트는 Provider로 감싸져 Context의 기본값을 사용하지 않는다. Provider 컴포넌트가 전달하는 데이터를 사용하며, 데이터가 없을 땐 Provider에 값을 직접 넣어준다.</p>
<p>Consumer 컴포넌트는 가장 가까운 Provider에서 값을 받으므로 중간에 다른 Provider가 있다면 그 아래 자식은 다른 Provider에서 값을 받아 사용한다.</p>
<h3 id="context-수정하기">Context 수정하기</h3>
<pre><code class="language-jsx">import React, { useState, useContext } from &#39;react&#39;;
import styled from &#39;styled-components/native&#39;;
import UserContext, { UserConsumer } from &#39;../contexts/User&#39;;

const StyledInput = styled.TextInput`
  border: 1px solid #606060;
  width: 250px;
  padding: 10px 15px;
  margin: 10px;
  font-size: 24px;
`;

const Input = () =&gt; {
  const [name, setName] = useState(&#39;&#39;);

  return (
  &lt;UserConsumer&gt;
  {({ dispatch}) =&gt; {
      return (
    &lt;StyledInput
      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;
  ); }}
  &lt;/UserConsumer&gt;
  );
};

export default Input;</code></pre>
<p>useContext(UserContext)로 현재 컨텍스트에서 dispatch를 꺼낸다. 사용자가 텍스트 입력하면 name 상태에 저장된다. 엔터(완료) 누르면 dispatch(name)을 호출해서 Context 안의 state를 변경한다. 그 후 setName(&#39;&#39;)으로 입력창을 비워준다.</p>
<h2 id="usecontext">useContext</h2>
<p>Consumer 컴포넌트를 사용하지 않고 Context의 내용을 사용할 수 있게 해준다. </p>
<pre><code class="language-jsx">...

  const { dispatch } = useContext(UserContext);

  return (
    &lt;StyledInput
      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>
]]></description>
        </item>
        <item>
            <title><![CDATA[[처음부터 배우는 리액트 네이티브]Hooks]]></title>
            <link>https://velog.io/@choi-ss-choco/%EC%B2%98%EC%9D%8C%EB%B6%80%ED%84%B0-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C</link>
            <guid>https://velog.io/@choi-ss-choco/%EC%B2%98%EC%9D%8C%EB%B6%80%ED%84%B0-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C</guid>
            <pubDate>Fri, 21 Nov 2025 13:31:57 GMT</pubDate>
            <description><![CDATA[<h1 id="hooks">Hooks</h1>
<p>이전에는 컴포넌트의 상태관리나 생명주기에 따라 작업을 수행할 때에는 클래스형 컴포넌트만 사용해야 했다. 그러나 Hooks이 등장하며 함수형 컴포넌트에서도 상태를 관리할 수 있게 되었다.</p>
<h2 id="usestate">useState</h2>
<p>useState를 호출하면 변수와 그 변수를 수정할 수 있는 새터 함수를 배열로 반환한다. 관리해야 하는 상태의 수만큼 여러번 사용할 수 있다. 상태 관리 변수는 반드시 새터 함수를 이용해 값을 변경해야 한다.</p>
<p>새터 함수를 사용하는 방법은 두가지가 있다.</p>
<ol>
<li><p>새터 함수에 변경될 상태의 값을 전달하기</p>
</li>
<li><p>새터 함수의 파라미터에 함수를 전달하기</p>
<pre><code class="language-jsx"> setState(prevState =&gt; {});</code></pre>
<p> 새터 함수는 비동기로 동작하기 때문에 상태 변경이 여러번 일어나면 상태가 변경되기 전에 또다시 상태에 대한 업데이트가 실행되는 상황이 발생한다. 이럴 땐 새터함수에 함수를 인자로 전달하여 이전 상태값을 이용한다.</p>
</li>
</ol>
<pre><code class="language-jsx">&lt;Button
    title=&quot;+&quot;
    onPress={() =&gt; {
        setCount(count + 1);
        setCount(count + 1);
    }}
/&gt;</code></pre>
<pre><code class="language-jsx">&lt;Button
  title=&quot;+&quot;
  onPress={() =&gt; {
    setCount(prevCount =&gt; prevCount + 1);
    setCount(prevCount =&gt; prevCount + 1);
  }}
/&gt;</code></pre>
<h2 id="useeffect">useEffect</h2>
<p>컴포넌트가 렌더링될 때마다 원하는 작업이 실행되도록 설정할 수 있는 기능이다. useEffect의 첫번째 파라미터로 전달된 함수는 조건을 만족할 때마다 호출되고, 두번째 파라미터로 전달되는 배열로 호출되는 조건을 설정할 수 있다. </p>
<pre><code class="language-jsx">useEffect(() =&gt; {
  console.log(`name: ${name}, email: ${email}\n`);
}, [email]);
//이메일이 변경될 때마다 실행된다</code></pre>
<p>실행조건이 컴포넌트가 마운트 될 때로 설정한다면 아래와 같다</p>
<pre><code class="language-jsx">useEffect(() =&gt; {
  console.log(&#39;\n===== Form Component Mount =====\n&#39;);
, []);</code></pre>
<p>언마운트 될 때 실행하려면 실행조건, 첫번째 파라미터를을 빈 배열로 만든다.</p>
<pre><code class="language-jsx">useEffect(() =&gt; {
  console.log(&#39;\n===== Form Component Mount =====\n&#39;);
  refName.current.focus();
  return () =&gt; console.log(&#39;\n===== Form Component Unmount =====\n&#39;);
}, []);</code></pre>
<h2 id="useref">useRef</h2>
<p>특정 컴포넌트를 선택해야 하는 경우 사용된다(ex. 포커스를 옮길 때)</p>
<p>useRef를 사용할 때 주의할 점이 있다.</p>
<ol>
<li>컴포넌트의 ref로 지정하면 생성된 변수에 값이 저장되는 것이 아니라 .current 프로퍼티에 값을 담는 것이다.</li>
<li>useState를 이용한 상태와 달리 useRef내용이 변경되어도 컴포넌트는 다시 렌더링 되지 않는다</li>
</ol>
<pre><code class="language-jsx">return (
    &lt;&gt;
      &lt;StyledText&gt;Name: {name}&lt;/StyledText&gt;
      &lt;StyledText&gt;Email: {email}&lt;/StyledText&gt;
      &lt;StyledTextInput
        value={name}
        onChangeText={text =&gt; setName(text)}
        placeholder=&quot;name&quot;
        ref={refName}
        returnKeyType=&quot;next&quot;
        onSubmitEditing={() =&gt; refEmail.current.focus()}
      /&gt;
      &lt;StyledTextInput
        value={email}
        onChangeText={text =&gt; setEmail(text)}
        placeholder=&quot;email&quot;
        ref={refEmail}
        returnKeyType=&quot;done&quot;
      /&gt;
    &lt;/&gt;
  );
};

export default Form;</code></pre>
<h2 id="usememo">useMemo</h2>
<p>동일한 반복 수행을 제거하여 성능을 최적화시킨다. 첫번째 파라미터에는 함수를 전달하고 두번째 파라미터에는 함수 실행 조건을 배열로 전달한다.</p>
<pre><code class="language-jsx">import React, { useState, useMemo } from &#39;react&#39;;
import styled from &#39;styled-components/native&#39;;
import Button from &#39;./Button&#39;;

const StyledText = styled.Text`
  font-size: 24px;
`;

const getLength = text =&gt; {
  console.log(`Target Text: ${text}`);
  return text.length;
};

const list = [&#39;JavaScript&#39;, &#39;Expo&#39;, &#39;Expo&#39;, &#39;React Native&#39;];

let idx = 0;
const Length = () =&gt; {
  const [text, setText] = useState(list[0]);

  const _onPress = () =&gt; {
    ++idx;
    if (idx &lt; list.length) setText(list[idx]);
  };
  const length = useMemo(() =&gt; getLength(text), [text]);
    /*사용하지 않으면 값의 변경이 없더라도 계속 같은 문자열의 길이를 구한다.*/
  return (
    &lt;&gt;
      &lt;StyledText&gt;Text: {text}&lt;/StyledText&gt;
      &lt;StyledText&gt;Length: {length}&lt;/StyledText&gt;
      &lt;Button title=&quot;Get Length&quot; onPress={_onPress} /&gt;
    &lt;/&gt;
  );
};

export default Length;</code></pre>
<h2 id="커스텀-hooks-만들기">커스텀 Hooks 만들기</h2>
<p>특정 API에 GET요청을 보내고 응답을 받는 함수를 만들어보자. 이번에는 Fetch를 이용하여 uesFetch라는 이름의 Hook을 만들 것이다.</p>
<p>hooks/useFetch.js</p>
<pre><code class="language-jsx">import { useState, useEffect } from &#39;react&#39;;

export const useFetch = url =&gt; {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [inProgress, setInProgress] = useState(false);

  useEffect(() =&gt; {
    const fetchData = async () =&gt; {
      try {
        setInProgress(true);
        const res = await fetch(url);
        const result = await res.json();
        if (res.ok) {
          setData(result);
          setError(null);
        } else {
          throw result;
        }
      } catch (error) {
        setError(error);
      } finally {
        setInProgress(false);
      }
    };
    fetchData();
  }, []);

  return { data, error, inProgress };
};</code></pre>
<blockquote>
<p>📝  실행 흐름 요약</p>
<ol>
<li>컴포넌트가 처음 렌더링되면 useEffect가 실행된다.</li>
<li>fetchData 함수가 호출되고 inProgress가 true로 바뀐다.</li>
<li>fetch(url)을 실행하고 응답을 JSON으로 변환한다.</li>
<li>응답이 성공(res.ok)이라면 data에 결과를 저장하고 error를 null로 만든다.</li>
<li>응답이 실패하면 throw되어 catch로 넘어가고 error 상태에 저장된다.</li>
<li>try/catch가 끝나면 finally가 실행되어 inProgress가 false로 바뀐다.</li>
<li>최종적으로 훅은 data, error, inProgress 값을 반환한다.</li>
</ol>
</blockquote>
<p>components/Dogs.js</p>
<pre><code class="language-jsx">import React from &#39;react&#39;;
import styled from &#39;styled-components/native&#39;;
import { useFetch } from &#39;../hooks/useFetch&#39;;

const StyledImage = styled.Image`
  background-color: #7f8c8d;
  width: 300px;
  height: 300px;
`;
const ErrorMessage = styled.Text`
  font-size: 18px;
  color: #e74c3c;
`;
const LoadingMessage = styled.Text`
  font-size: 18px;
  color: #2ecc71;
`;

const URL = &#39;https://dog.ceo/api/breeds/image/random&#39;;
const Dog = () =&gt; {
  const { data, error, inProgress } = useFetch(URL);

  return (
    &lt;&gt;
      {inProgress &amp;&amp; (
        &lt;LoadingMessage&gt;The API request is in progress&lt;/LoadingMessage&gt;
      )}
      &lt;StyledImage source={data?.message ? { uri: data.message } : null} /&gt;
      &lt;ErrorMessage&gt;{error?.message}&lt;/ErrorMessage&gt;
    &lt;/&gt;
  );
};

export default Dog;</code></pre>
<blockquote>
<p>📝 실행 흐름 요약</p>
<ol>
<li>Dog 컴포넌트가 렌더링되면 useFetch(URL)이 호출된다.</li>
<li>useFetch 내부의 useEffect가 실행되어 API 요청이 시작되고 inProgress가 true가 된다.</li>
<li>fetch로 강아지 랜덤 이미지 API를 호출하고 JSON 데이터를 받아온다.</li>
<li>요청이 성공하면 data에 응답이 저장되고 error는 null이 된다.</li>
<li>요청이 실패하면 error에 에러 정보가 저장된다.</li>
<li>요청이 끝나면 inProgress가 false로 바뀐다.</li>
<li>Dog 컴포넌트는 상태에 따라 다음을 렌더링한다:<ul>
<li>inProgress가 true일 때 로딩 메시지를 표시한다.</li>
<li>data.message가 있으면 해당 url의 이미지를 StyledImage에 표시한다.</li>
<li>error가 있으면 ErrorMessage로 에러 메시지를 표시한다.</li>
</ul>
</li>
</ol>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[처음부터 배우는 리액트 네이티브]할 일 관리 어플리케이션]]></title>
            <link>https://velog.io/@choi-ss-choco/%EC%B2%98%EC%9D%8C%EB%B6%80%ED%84%B0-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C%ED%95%A0-%EC%9D%BC-%EA%B4%80%EB%A6%AC-%EC%96%B4%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98</link>
            <guid>https://velog.io/@choi-ss-choco/%EC%B2%98%EC%9D%8C%EB%B6%80%ED%84%B0-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C%ED%95%A0-%EC%9D%BC-%EA%B4%80%EB%A6%AC-%EC%96%B4%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98</guid>
            <pubDate>Fri, 21 Nov 2025 13:30:48 GMT</pubDate>
            <description><![CDATA[<h1 id="할-일-관리-애플리케이션">할 일 관리 애플리케이션</h1>
<p>📎 <a href="https://github.com/choi-day/react-native-todo">https://github.com/choi-day/react-native-todo</a></p>
<h2 id="프로젝트-준비하기">프로젝트 준비하기</h2>
<p>프로젝트를 생성한다</p>
<pre><code class="language-bash">expo init react-naitive-todo</code></pre>
<p>스타일드 컴포넌트 라이브러리와 prop-types 라이브러리를 설치한다</p>
<pre><code class="language-bash">npm install styled-components prop-types</code></pre>
<p>src/App.js 작성</p>
<pre><code class="language-jsx">import React from &quot;react&quot;;
import styled, {ThemeProvider} from &#39;styled-components/native&#39;
import {theme} from &#39;./theme&#39;;

const Container = styled.View`
    flex: 1;
    background-color: ${({theme}) =&gt; theme.background};
    align-items: center;
    justify-content: center;
`;

export default function App() {
    return (
        &lt;ThemeProvider theme={theme}&gt;
            &lt;Container&gt;&lt;/Container&gt;
        &lt;/ThemeProvider&gt;
    )
}</code></pre>
<p>App.js 수정</p>
<pre><code class="language-jsx">import App from &#39;./src/App&#39;

export default App;</code></pre>
<blockquote>
<p>📝 현재 동작</p>
</blockquote>
<ol>
<li>App을 ThemeProvider로 감싼다.</li>
<li>theme 안에 정의된 값들이 모든 styled-component에 전달된다.</li>
<li>Container는 theme.background 색을 배경으로 사용한다.</li>
<li>flex 1로 화면 전체를 채우고, 자식을 중앙에 정렬한다.<blockquote>
</blockquote>
</li>
</ol>
<h2 id="타이틀-만들기">타이틀 만들기</h2>
<p>App.js 수정(title 삽입)</p>
<pre><code class="language-jsx">import React from &quot;react&quot;;
import { StatusBar } from &quot;react-native&quot;;
import styled, {ThemeProvider} from &#39;styled-components/native&#39;
import {theme} from &#39;./theme&#39;;

const Container = styled.SafeAreaView`
    flex: 1;
    background-color: ${({theme}) =&gt; theme.background};
    align-items: center;
    justify-content: flex-start;
`;

const Title = styled.Text`
    font-size: 40px;
    font-weight: 600;
    color: ${({theme}) =&gt; theme.main};
    align-self: flex-start;
    margin: 20px;
`;

export default function App() {
    return (
        &lt;ThemeProvider theme={theme}&gt;
            &lt;Container&gt;
                &lt;StatusBar
                barStyle=&quot;light-content&quot;
                backgroundColor={theme.background}
                /&gt;
                &lt;Title&gt;TODO List&lt;/Title&gt;
            &lt;/Container&gt;
        &lt;/ThemeProvider&gt;
    );
}</code></pre>
<blockquote>
<p>📝 현재 동작</p>
<ol>
<li>화면은 SafeArea 위에서 시작됨 (노치/상단바 안 겹침)</li>
<li>StatusBar 텍스트는 흰색 (light-content)</li>
<li>Android에서는 StatusBar 배경도 theme.background 색으로 변경됨</li>
<li>배경은 theme.background 색</li>
<li>TODO List 제목은 왼쪽 상단에 크게 표시됨</li>
</ol>
</blockquote>
<h2 id="input-컴포넌트-만들기">Input 컴포넌트 만들기</h2>
<p>component/input.js 작성</p>
<pre><code class="language-jsx">import React from &#39;react&#39;;
import styled from &#39;styled-components/native&#39;;
import { Dimensions, useWindowDimensions } from &#39;react-native&#39;;
import PropTypes from &#39;prop-types&#39;;
import { theme } from &#39;../theme&#39;;

const StyledInput = styled.TextInput.attrs(({ theme }) =&gt; ({
    placeholderTextColor: theme.main,
}))`
    width: ${({ width }) =&gt; width-40}px;
    height: 60px;
    margin: 3px 0;
    padding: 15px 20px;
    border-radius: 10px;
    background-color: ${({theme}) =&gt; theme.itemBackground};
    font-size: 25px;
    color: ${({theme})=&gt; theme.text};
`;

const Input = ({placeholder, value, onChangeText, onSubmitEditing,}) =&gt; {
    const width = useWindowDimensions().width

    return( &lt;StyledInput 
            width = {width} 
            placeholder={placeholder} 
            maxLength={50}
            autoCapitalize=&#39;none&#39;
            autoCorrect={false}
            returnKeyType=&#39;done&#39;
            keyboardAppearance=&#39;dark&#39;
            value={value}
            onChangeText={onChangeText}
            onSubmitEditing={onSubmitEditing}
            /&gt;
        );
};

Input.propTypes = {
    placeholder: PropTypes.string,
    value: PropTypes.string.isRequired,
    onChangeText: PropTypes.func.isRequired,
    onSubmitEditing: PropTypes.func.isRequired,
  };

export default Input;</code></pre>
<blockquote>
<p><strong>📝</strong> Input 컴포넌트 동작</p>
<ol>
<li>현재 화면 너비를 자동으로 가져옴 (useWindowDimensions) 
→ 기기 크기에 따라 입력창 너비 자동 조절</li>
<li>입력창 폭을 화면 너비 - 40px 로 설정</li>
<li>placeholder 색, 배경색, 글자색을 theme에서 가져와 적용</li>
<li>입력 이벤트를 외부에서 제어하도록 설정됨<ul>
<li>value → 현재 입력값</li>
<li>onChangeText → 입력할 때마다 실행</li>
<li>onSubmitEditing → 엔터(완료) 눌렀을 때 실행</li>
</ul>
</li>
<li>TextInput 공통 설정 적용됨<ul>
<li>자동대문자 없음</li>
<li>자동완성/자동수정 꺼짐</li>
<li>returnKeyType=‘done’</li>
<li>iOS 다크 키보드 사용</li>
</ul>
</li>
</ol>
</blockquote>
<p>App.js 수정</p>
<pre><code class="language-jsx">export default function App() {
    const [newTask, setNewTask] = useState(&#39;&#39;);

    const _addTask = () =&gt; {
        alert(`Add: ${newTask}`);
        setNewTask(&#39;&#39;);
    };

    const _handleTextChange = text =&gt; {
        setNewTask(text);
    }


...

&lt;Input 
placeholder = &quot;+ Add a Task&quot;
value = {newTask}
onChangeText={_handleTextChange}
onSubmitEditing={_addTask}
/&gt;</code></pre>
<blockquote>
<p>📝 수정된 동작 요약</p>
<ol>
<li><p>사용자가 입력창에 적는 텍스트가 newTask 에 저장됨.</p>
</li>
<li><p>입력창에서 글자 입력할 때</p>
<p> → onChangeText → _handleTextChange 실행</p>
<p> → newTask 값이 실시간 업데이트됨.</p>
</li>
<li><p>엔터(Submit) 키를 누르면</p>
<p> → onSubmitEditing → _addTask 실행</p>
<p> → 현재 입력된 newTask를 alert로 표시</p>
<p> → 입력이 완료되면 newTask 값을 빈 문자열로 초기화(입력창 비워짐).</p>
</li>
<li><p>Input 컴포넌트는 newTask를 value로 받아 표시함</p>
<p> → 상태 변화가 입력창에 실시간 반영됨.</p>
</li>
</ol>
</blockquote>
<h2 id="할-일-목록-만들기">할 일 목록 만들기</h2>
<p>아이콘이 될 이미지를 assets/icons 아래 다운 받는다. 파일 명을 동일한 이름으로 사용하면서 뒤에 @2x, @3x 를 붙이면 자동으로 화면에 알맞은 이미지를 불러와 사용한다</p>
<p><img src="https://velog.velcdn.com/images/choi-ss-choco/post/1442b020-4845-40d6-8ab9-ed67e8955f31/image.png" alt=""></p>
<p>src/images.js</p>
<pre><code class="language-jsx">import CheckBoxOutline from &#39;../assets/icons/check_box_outline.png&#39;;
import CheckBox from &#39;../assets/icons/check_box.png&#39;;
import DeleteForever from &#39;../assets/icons/delete_forever.png&#39;;
import Edit from &#39;../assets/icons/edit.png&#39;;

export const images = {
  uncompleted: CheckBoxOutline,
  completed: CheckBox,
  delete: DeleteForever,
  update: Edit,
};</code></pre>
<blockquote>
<p>📝 images.js 동작 요약</p>
<ol>
<li>각 아이콘 이미지를 파일에서 불러온다.<ul>
<li>체크박스 비활성</li>
<li>체크박스 활성</li>
<li>삭제 아이콘</li>
<li>수정 아이콘</li>
</ul>
</li>
<li>불러온 이미지들을 하나의 객체(images)에 정리해서 저장한다.</li>
</ol>
</blockquote>
<p>components/IconButton.js</p>
<pre><code class="language-jsx">import React from &#39;react&#39;;
import { TouchableOpacity } from &#39;react-native&#39;;
import styled from &#39;styled-components/native&#39;;
import PropTypes from &#39;prop-types&#39;;
import { images } from &#39;../images&#39;;

const Icon = styled.Image`
  tint-color: ${({ theme, completed }) =&gt;
    completed ? theme.done : theme.text};
  width: 30px;
  height: 30px;
  margin: 10px;
`;

const IconButton = ({ type, onPressOut}) =&gt; {
  return (
    &lt;TouchableOpacity onPressOut={_onPressOut}&gt;
      &lt;Icon source={type} completed={completed} /&gt;
    &lt;/TouchableOpacity&gt;
  );
};

IconButton.propTypes = {
  type: PropTypes.oneOf(Object.values(images)).isRequired,
  onPressOut: PropTypes.func,
};

export default IconButton;</code></pre>
<blockquote>
<p>📝 코드 동작 요약</p>
<ol>
<li>IconButton은 TouchableOpacity 를 사용해 터치 가능한 버튼을 만든다.</li>
<li>버튼 안에는 type으로 전달된 아이콘 이미지(source) 를 표시한다.</li>
<li>아이콘은 styled-components로 스타일링되어 30×30 크기 + margin 10 으로 표시된다.</li>
<li>버튼을 떼는 순간(onPressOut) 전달된 함수가 실행된다.</li>
<li>type은 반드시 images 객체 안의 아이콘 중 하나여야 하고, PropTypes로 타입 검사를 한다.</li>
</ol>
</blockquote>
<p>components/task.js</p>
<pre><code class="language-jsx">import React, { useState } from &#39;react&#39;;
import styled from &#39;styled-components/native&#39;;
import PropTypes from &#39;prop-types&#39;;
import IconButton from &#39;./IconButton&#39;;
import { images } from &#39;../images&#39;;

const Container = styled.View`
    flex-direction: row;
    align-items: center;
    background-color: ${({ theme }) =&gt; theme.itemBackground};
    border-radius: 10px;
    padding: 5px;
    margin: 3px 0px;
`;

const Contents = styled.Text`
    flex: 1;
    font-size: 24px;
    color: ${({ theme }) =&gt; theme.text};
`;

const Task = ({ text }) =&gt; {
    return (
    &lt;Container&gt;
        &lt;IconButton type={images.uncompleted}/&gt;
        &lt;Contents&gt;{text}&lt;/Contents&gt;
        &lt;IconButton type={images.update} /&gt;
        &lt;IconButton type={images.delete} /&gt;
    &lt;/Container&gt;
    );
};

Task.propTypes = {
    text: PropTypes.string.isRequired
};

export default Task;</code></pre>
<blockquote>
<p>📝 Task 컴포넌트 동작 요약</p>
<ol>
<li>Task는 한 줄짜리 할 일 아이템을 표시하는 컴포넌트이다.</li>
<li>왼쪽에는 완료 상태를 나타내기 위한 체크박스 아이콘이 배치된다.</li>
<li>가운데에는 text prop으로 전달된 할 일 내용이 표시되며, flex:1로 공간을 넓게 차지한다.</li>
<li>오른쪽에는 수정 아이콘과 삭제 아이콘이 각각 버튼 형태로 배치된다.</li>
<li>text는 반드시 문자열이어야 하고 PropTypes로 타입 검사를 한다.</li>
</ol>
</blockquote>
<h2 id="기능-구현하기">기능 구현하기</h2>
<h3 id="할-일-추가하기">할 일 추가하기</h3>
<p>App.js 수정</p>
<pre><code class="language-jsx">export default function App() {
  const width = Dimensions.get(&#39;window&#39;).width;
  const [newTask, setNewTask] = useState(&#39;&#39;);
  const [tasks, setTasks] = useState({
    &#39;1&#39;: { id: &#39;1&#39;, text: &#39;Hanbit&#39;, completed: false },
    &#39;2&#39;: { id: &#39;2&#39;, text: &#39;React Native&#39;, completed: true },
    &#39;3&#39;: { id: &#39;3&#39;, text: &#39;React Native Sample&#39;, completed: false },
    &#39;4&#39;: { id: &#39;4&#39;, text: &#39;Edit TODO Item&#39;, completed: false },
  });

  const _addTask = () =&gt; {
    const ID = Date.now().toString();
    const newTaskObject = {
      [ID]: { id: ID, text: newTask, completed: false },
    };
    setNewTask(&#39;&#39;);
    setTasks({ ...tasks, ...newTaskObject });

    ...

   return (
    ...
    &lt;List width={width}&gt;
      {Object.values(tasks)
        .reverse()
        .map(item =&gt; (
          &lt;Task key={item.id} text={item.text} /&gt;
        ))}
    &lt;/List&gt;
    ...
  );
}</code></pre>
<blockquote>
<p>📝  ****추가 기능 동작 요약</p>
<ol>
<li><p>기존 newTask만 관리하던 상태에 tasks라는 할 일 목록 상태 객체를 추가한다.</p>
<p> → 각 할 일은 id, text, completed 필드를 가진다.</p>
</li>
<li><p>_addTask 함수는 Date.now().toString()으로 고유 ID를 생성하고,</p>
<p> 그 ID를 key로 갖는 새 할 일 객체(newTaskObject)를 만든다.</p>
</li>
<li><p>새로 만든 할 일을 기존 tasks와 합쳐서 상태를 갱신한다.</p>
<p> → 입력창은 setNewTask(&#39;&#39;)로 비워져서 다시 입력할 수 있게 된다.</p>
</li>
<li><p><List> 영역에서는Object.values(tasks)로 상태에 있는 모든 할 일을 꺼내 동적 렌더링한다.</p>
</li>
<li><p>.reverse()를 사용해 가장 최근에 추가한 할 일일수록 위에 보이도록 목록 순서를 뒤집은 뒤 렌더링한다.</p>
</li>
</ol>
</blockquote>
<h3 id="수정하기">수정하기</h3>
<p>App.js</p>
<pre><code class="language-jsx">const _deleteTask = id =&gt; {
  const currentTasks = Object.assign({}, tasks);
  delete currentTasks[id];
  setTasks(currentTasks);
};

...

&lt;Task 
  key={item.id}
  item={item}
  deleteTask={_deleteTask}
/&gt;</code></pre>
<p>Task.js</p>
<pre><code class="language-jsx">&lt;IconButton 
  type={images.delete}
  id={item.id}
  onPressOut={deleteTask}
/&gt;</code></pre>
<p>IconButton.js</p>
<pre><code class="language-jsx">const IconButton = ({ type, onPressOut, id }) =&gt; {
  const _onPressOut = () =&gt; {
    if (typeof onPressOut === &#39;function&#39;) onPressOut(id);
  };

  return (
    &lt;TouchableOpacity onPressOut={_onPressOut}&gt;
      &lt;Icon source={type} /&gt;
    &lt;/TouchableOpacity&gt;
  );
};</code></pre>
<blockquote>
<p>📝 삭제 기능 동작 요약</p>
<ol>
<li>삭제 아이콘을 누르면 IconButton 내부에서 id를 포함한 onPressOut이 실행된다.</li>
<li>Task 컴포넌트는 deleteTask 함수에 해당 id를 전달한다.</li>
<li>App.js의 deleteTask 함수는 tasks 객체를 복사한 뒤 해당 id를 가진 항목을 삭제한다.</li>
<li>삭제된 tasks 객체를 setTasks로 다시 저장하여 화면이 갱신된다.</li>
<li>결과적으로 터치한 할 일 항목이 목록에서 사라진다.</li>
</ol>
</blockquote>
<h3 id="완료-기능-구현">완료 기능 구현</h3>
<p>App.js</p>
<pre><code class="language-jsx">const _toggleTask = id =&gt; {
  const currentTasks = Object.assign({}, tasks);
  currentTasks[id][&#39;completed&#39;] = !currentTasks[id][&#39;completed&#39;];
  setTasks(currentTasks);
};

...

&lt;Task 
  key={item.id} 
  item={item}
  deleteTask={_deleteTask}
  toggleTask={_toggleTask}
/&gt;</code></pre>
<p>Task.js</p>
<pre><code class="language-jsx">&lt;IconButton 
  type={item.completed ? images.completed : images.uncompleted}
  id={item.id}
  onPressOut={toggleTask}
  completed={item.completed}
/&gt;

...

&lt;Contents completed={item.completed}&gt;{item.text}&lt;/Contents&gt;

...

const Contents = styled.Text`
  flex: 1;
  font-size: 24px;
  color: ${({ theme, completed }) =&gt; (completed ? theme.done : theme.text)};
  text-decoration-line: ${({ completed }) =&gt; 
    completed ? &#39;line-through&#39; : &#39;none&#39;};
`;</code></pre>
<p>IconButton.js</p>
<pre><code class="language-jsx">const IconButton = ({ type, onPressOut, id, completed }) =&gt; {
  const _onPressOut = () =&gt; {
    if (typeof onPressOut === &#39;function&#39;) onPressOut(id);
  };

  return (
    &lt;TouchableOpacity onPressOut={_onPressOut}&gt;
      &lt;Icon source={type} completed={completed} /&gt;
    &lt;/TouchableOpacity&gt;
  );
};</code></pre>
<blockquote>
<p>📝 완료 기능 동작 요약</p>
<ol>
<li>체크 아이콘을 누르면 IconButton 내부에서 id를 포함한 onPressOut 함수가 실행된다.</li>
<li>Task 컴포넌트는 전달받은 toggleTask 함수에 해당 id를 전달한다.</li>
<li>App.js의 toggleTask 함수는 tasks 객체에서 해당 id의 completed 값을 반전시킨다.</li>
<li>setTasks로 업데이트된 tasks를 저장하면 UI가 자동으로 다시 렌더링된다.</li>
<li>완료된 항목은 체크박스 이미지가 변경되고, 텍스트 색상과 스타일이 line-through로 바뀐다.</li>
</ol>
</blockquote>
<h3 id="수정-기능-구현">수정 기능 구현</h3>
<p>App.js</p>
<pre><code class="language-jsx">const [isEditing, setIsEditing] = useState(false);
const [currentTask, setCurrentTask] = useState({});

...

const _startEditing = item =&gt; {
  setIsEditing(true);
  setCurrentTask(item);
};

...

const _updateTask = () =&gt; {
  const updatedTasks = Object.assign({}, tasks);
  updatedTasks[currentTask.id] = currentTask;
  saveTasks(updatedTasks);
  setIsEditing(false);
  setCurrentTask({});
};</code></pre>
<p>Task.js</p>
<pre><code class="language-jsx">&lt;IconButton 
  type={images.update}
  onPressOut={() =&gt; startEditing(item)}
/&gt;</code></pre>
<p>Input.js</p>
<pre><code class="language-jsx">&lt;Input
  placeholder=&quot;+ Add a Task&quot;
  value={isEditing ? currentTask.text : newTask}
  onChangeText={text =&gt;
    isEditing
      ? setCurrentTask({ ...currentTask, text })
      : setNewTask(text)
  }
  onSubmitEditing={isEditing ? updateTask : addTask}
/&gt;</code></pre>
<blockquote>
<p><strong>📝</strong> 수정 기능 동작 요약</p>
<ol>
<li>수정 기능을 위해 isEditing 상태와 현재 수정 중인 할 일을 저장하는 currentTask 상태를 추가한다.</li>
<li>수정 버튼을 누르면 startEditing이 실행되어 수정 모드로 전환되고, 입력창에는 해당 할 일의 텍스트가 채워진다.</li>
<li>입력창에서 텍스트를 변경하면 currentTask.text 값만 업데이트된다.</li>
<li>엔터(Submit)를 누르면 updateTask가 실행되어 해당 id의 할 일을 새로운 텍스트로 덮어쓴다.</li>
<li>수정 후에는 isEditing이 false가 되어 입력창이 다시 기본 입력 모드로 돌아간다.</li>
</ol>
</blockquote>
<h2 id="부가기능">부가기능</h2>
<h3 id="데이터-저장하고-불러오기">데이터 저장하고 불러오기</h3>
<p>AsyncStorage를 이용해 로컬에 데이터를 저장하고 불러올 수 있다. 비동기로 동작하며 문자열로 된 키-값의 형태의 데이터를 기기에 저장하고 불러오는 기능을 제공한다. </p>
<pre><code class="language-bash">expo install @react-native-community/async-storage</code></pre>
<pre><code class="language-jsx">const _saveTasks = async tasks =&gt; {
    try {
    await AsyncStorage.setItem(&#39;tasks&#39;, JSON.stringify(tasks));
    setTasks(tasks);
    } catch (e) {
    console.
    error(e);
    }
}</code></pre>
<p>이후 setTasks를 _saveTasks로 바꿔준다</p>
<blockquote>
<p>📝 데이터 저장 동작 요약</p>
<ol>
<li>AsyncStorage에서 &#39;tasks&#39; 키로 저장된 데이터를 가져온다.</li>
<li>불러온 문자열 데이터를 JSON.parse 해서 tasks 상태에 넣고, 에러가 나면 콘솔에 출력한다.</li>
<li>할 일을 추가/삭제/완료 토글할 때마다 _saveTasks를 통해 tasks 상태를 업데이트하면서 동시에 AsyncStorage에 다시 JSON.stringify(tasks) 형태로 저장한다.</li>
</ol>
</blockquote>
<pre><code class="language-jsx">const _loadTasks = async () =&gt; {
    const loadedTasks = await AsyncStorage.getItem(&#39;tasks&#39;);
    setTasks(JSON.parse(loadedTasks || &#39;{}&#39;));
};</code></pre>
<blockquote>
<p>📝 데이터 불러오기 동작 요약</p>
<ol>
<li>AsyncStorage에서 &#39;tasks&#39; 키로 저장된 데이터를 읽고, 없으면 빈 객체 {}를 기본값으로 사용한다.</li>
<li>가져온 문자열 데이터를 JSON.parse 후 tasks 상태에 넣어 화면에서 사용할 수 있게 만든다.</li>
</ol>
</blockquote>
<p><img src="https://velog.velcdn.com/images/choi-ss-choco/post/b5528e9c-1599-4482-aea0-cce802918a35/image.gif" alt=""></p>
<hr>
<p>중간에 stylecomponents 오류가 나서 한참을 삽질하다가 아래 글을 참고하여 고쳤다... 버전때문에 오류가 많이 나는 듯 하다 </p>
<p><a href="https://github.com/styled-components/styled-components/issues/5576">https://github.com/styled-components/styled-components/issues/5576</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[처음부터 배우는 리액트 네이티브]스타일링]]></title>
            <link>https://velog.io/@choi-ss-choco/%EC%B2%98%EC%9D%8C%EB%B6%80%ED%84%B0-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C%EC%8A%A4%ED%83%80%EC%9D%BC%EB%A7%81</link>
            <guid>https://velog.io/@choi-ss-choco/%EC%B2%98%EC%9D%8C%EB%B6%80%ED%84%B0-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C%EC%8A%A4%ED%83%80%EC%9D%BC%EB%A7%81</guid>
            <pubDate>Thu, 13 Nov 2025 18:29:58 GMT</pubDate>
            <description><![CDATA[<h1 id="스타일링">스타일링</h1>
<h2 id="스타일링-1">스타일링</h2>
<h3 id="인라인-스타일링">인라인 스타일링</h3>
<p>컴포넌트에 직접 스타일을 입력하는 방식이다</p>
<pre><code class="language-jsx">    &lt;View
      style={{
        flex: 1,
        backgroundColor: &#39;#fff&#39;,
        alignItems: &#39;center&#39;,
        justifyContent: &#39;center&#39;,
      }}
</code></pre>
<p>어떤 스타일이 적용되는지 잘 보이지만 비슷한 역할을 하는 컴포넌트에 동일한 코드가 반복되고 어떤 스타일인지 명확하게 이해하기 힘들다.</p>
<h3 id="클래스-스타일링">클래스 스타일링</h3>
<p>스타일시트에 정의된 스타일을 사용하는 방법이다.</p>
<pre><code class="language-jsx">const App = () =&gt; {
  return (
    &lt;View style={styles.container}&gt;
      &lt;Text style={[styles.text, { color: &#39;green&#39; }]}&gt;
        Inline Styling - Text
      &lt;/Text&gt;
      &lt;Text style={[styles.text, styles.error]}&gt;Inline Styling - Error&lt;/Text&gt;
    &lt;/View&gt;
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: &#39;#fff&#39;,
    alignItems: &#39;center&#39;,
    justifyContent: &#39;center&#39;,
  },
  text: {
    padding: 10,
    fontSize: 26,
    fontWeight: &#39;600&#39;,
    color: &#39;black&#39;,
  },
  error: {
    fontWeight: &#39;400&#39;,
    color: &#39;red&#39;,
  },
});

export default App;</code></pre>
<h3 id="여러개의-스타일-적용">여러개의 스타일 적용</h3>
<pre><code class="language-jsx">&lt;Text style={[styles.text, { color: &#39;green&#39; }]}&gt;
&lt;Text style={[styles.text, styles.error]}&gt;Inline Styling - Error&lt;/Text&gt;</code></pre>
<h3 id="외부-스타일-이용하기">외부 스타일 이용하기</h3>
<pre><code class="language-jsx">import React from &#39;react&#39;;
import { View, Text } from &#39;react-native&#39;;
import { viewStyles, textStyles } from &#39;./styles&#39;;

const App = () =&gt; {
  return (
    &lt;View style={viewStyles.container}&gt;
      &lt;Text style={[textStyles.text, { color: &#39;green&#39; }]}&gt;
        Inline Styling - Text
      &lt;/Text&gt;
      &lt;Text style={[textStyles.text, textStyles.error]}&gt;
        Inline Styling - Error
      &lt;/Text&gt;
    &lt;/View&gt;
  );
};

export default App;</code></pre>
<h2 id="리액트-네이티브-스타일">리액트 네이티브 스타일</h2>
<h3 id="flex와-범위">flex와 범위</h3>
<p>다양한 크기의 기기에 대응하기 위해 이용한다. flex는 0을 받으면 설정된 width나 height값에 따라 크기가 결정되고 양수인 경우 flex에 비례하여 크기가 조정된다.</p>
<h3 id="정렬">정렬</h3>
<ul>
<li>flexDirextion<ul>
<li>column: 세로 방향 정렬</li>
<li>column-reverse: 세로 방향 역순 정렬</li>
<li>row: 가로 방향 정렬</li>
<li>row-reverse: 가로 방향 역순 정렬</li>
</ul>
</li>
<li>justifyContent: flexDirextion에서 결정한 방향과 동일한 방향으로 정렬<ul>
<li>flex-start : 시작점에서부터 정렬 (기본값)</li>
<li>flex-end : 끝에서부터 정렬</li>
<li>center : 중앙 정렬</li>
<li>space-between : 컴포넌트 사이의 공간을 동일하게 만들어서 정렬</li>
<li>space-around : 컴포넌트 각각의 주변 공간을 동일하게 만들어서 정렬</li>
<li>space-evenly : 컴포넌트 사이와 양 끝에 동일한 공간을 만들어서 정렬</li>
</ul>
</li>
<li>alignItems: flexDirextion에서 결정한 방향과 수직이 되는 방향<ul>
<li>flex-start: 시작점에서부터 정렬</li>
<li>flex-end: 끝에서부터 정렬</li>
<li>center: 중앙 정렬</li>
<li>stretch: alignItems방향으로 컴포넌트</li>
</ul>
</li>
</ul>
<h3 id="그림자">그림자</h3>
<p>iOS에서는 shadowColor, shadowOffset, shadowOpacity, shadowRadius 를 사용하여 그림자를 표현하고, 안드로이드에서는 elevation이라는 속성을 사용한다. 따라서 플랫폼마다 다른 코드를 작성해야 한다. Platform 모듈을 이용한다.</p>
<pre><code class="language-jsx">import React from &#39;react&#39;;
import { StyleSheet, View, Platform } from &#39;react-native&#39;;

export default () =&gt; {
  return &lt;View style={styles.shadow}&gt;&lt;/View&gt;;
};

const styles = StyleSheet.create({
  shadow: {
    backgroundColor: &#39;#fff&#39;,
    width: 200,
    height: 200,
    ...Platform.select({
      ios: {
        shadowColor: &#39;#000&#39;,
        shadowOffset: {
          width: 10,
          height: 10,
        },
        shadowOpacity: 0.5,
        shadowRadius: 10,
      },
      android: {
        elevation: 20,
      },
    }),
  },
});</code></pre>
<h2 id="스타일드-컴포넌트">스타일드 컴포넌트</h2>
<p>styled.컴포넌트 이름 형태 뒤에 백틱 &#39; 을 사용하여 만든 문자열을 붙이고 그 안에 스타일을 지정한다. 태그드 탬플릿 리터럴이라고 한다. 완성된 컴포넌트를 상속받아 이용하는 경우도 있다.</p>
<pre><code class="language-jsx">const StyledText = styled.Text`
  color: #000;
  font-size: 20px;
  margin: 10px;
  padding: 10px;
`;

const ErrorText = styled(StyledText)`
  font-weight: 600;
  color: red;
`;</code></pre>
<h3 id="스타일-적용하기">스타일 적용하기</h3>
<pre><code class="language-jsx">import React from &#39;react&#39;;
import { StyleSheet, TouchableOpacity, Text } from &#39;react-native&#39;;

const styles = StyleSheet.create({
  container: {
    backgroundColor: &#39;#9b59b6&#39;,
    borderRadius: 15,
    paddingVertical: 15,
    paddingHorizontal: 40,
    marginVertical: 10,
    justifyContent: &#39;center&#39;,
  },
  title: {
    fontSize: 20,
    fontWeight: &#39;600&#39;,
    color: &#39;#fff&#39;,
  },
});

const Button = props =&gt; {
  return (
    &lt;TouchableOpacity
      style={[
        styles.container,
        { backgroundColor: props.title === &#39;Hanbit&#39; ? &#39;#3498db&#39; : &#39;#9b59b6&#39; },
      ]}
    &gt;
      &lt;Text style={styles.title}&gt;{props.title}&lt;/Text&gt;
    &lt;/TouchableOpacity&gt;
  );
};

export default Button;
*/</code></pre>
<pre><code class="language-jsx">import React from &#39;react&#39;;
import styled from &#39;styled-components/native&#39;;
import Button from &#39;./components/Button&#39;;

const Container = styled.View`
  flex: 1;
  background-color: #ffffff;
  align-items: center;
  justify-content: center;
`;

const App = () =&gt; {
  return (
      &lt;Container&gt;
        &lt;Button title=&quot;Hanbit&quot; /&gt;
        &lt;Button title=&quot;React Native&quot; /&gt;
      &lt;/Container&gt;
  );
};

export default App;</code></pre>
<h3 id="props-사용하기">props 사용하기</h3>
<p>스타일이 props값에 따라 바뀌는 경우, 기존 방식으로는 스타일 시트 안에서 props에 접근할 수 있는 방법이 없다. 스타일 컴포넌트에서는 스타일을 작성하는 백틱 안에서 props에 접근할 수 있다.</p>
<pre><code class="language-jsx">const ButtonContainer = styled.TouchableOpacity`
  background-color: ${props =&gt;
    props.title === &#39;Hanbit&#39; ? props.theme.blue : props.theme.purple};
  border-radius: 15px;
  padding: 15px 40px;
  margin: 10px 0px;
  justify-content: center;
`;
const Title = styled.Text`
  font-size: 20px;
  font-weight: 600;
  color: ${props =&gt; props.theme.text};
`;

const Button = props =&gt; {
  return (
    &lt;ButtonContainer title={props.title}&gt;
      &lt;Title&gt;{props.title}&lt;/Title&gt;
    &lt;/ButtonContainer&gt;
  );
};

export default Button;</code></pre>
<h3 id="attrs사용하기">attrs사용하기</h3>
<pre><code class="language-jsx">import React from &#39;react&#39;;
import styled from &#39;styled-components/native&#39;;

const StyledInput = styled.TextInput.attrs(props =&gt; ({
  placeholder: &#39;Enter a text...&#39;,
  placeholderTextColor: props.borderColor,
}))`
  width: 200px;
  height: 60px;
  margin: 5px;
  padding: 10px;
  border-radius: 10px;
  border: 2px;
  border-color: ${props =&gt; props.borderColor};
  font-size: 24px;
`;

const Input = props =&gt; {
  return &lt;StyledInput borderColor={props.borderColor} /&gt;;
};

export default Input;</code></pre>
<pre><code class="language-jsx">  return (
    &lt;ThemeProvider theme={isDark ? darkTheme : lightTheme}&gt;
      &lt;Container&gt;
        &lt;Switch value={isDark} onValueChange={_toggleSwitch} /&gt;
        &lt;Button title=&quot;Hanbit&quot; /&gt;
        &lt;Button title=&quot;React Native&quot; /&gt;
        &lt;Input borderColor=&quot;#3498db&quot; /&gt;
        &lt;Input borderColor=&quot;#9b59b6&quot; /&gt;
      &lt;/Container&gt;
    &lt;/ThemeProvider&gt;</code></pre>
<h3 id="themeprovider">ThemeProvider</h3>
<p>스타일 컴포넌트의 Context API를 활용해 애플리케이션 전체에서 스타일 컴포넌트를 이용할 때 미리 정의한 값들을 사용할 수 있도록 props로 전달한다. </p>
<pre><code class="language-jsx">export const theme = {
  purple: &#39;#9b59b6&#39;,
  blue: &#39;#3498db&#39;,
};</code></pre>
<pre><code class="language-jsx">import React, { useState } from &#39;react&#39;;
import { Switch } from &#39;react-native&#39;;
import styled, { ThemeProvider } from &#39;styled-components/native&#39;;
import Button from &#39;./components/Button&#39;;
import Input from &#39;./components/Input&#39;;
import { theme } from &#39;./theme&#39;;

...

const App = () =&gt; {
  return (
    &lt;ThemeProvider theme={theme}&gt;
...
      &lt;/Container&gt;
    &lt;/ThemeProvider&gt;
  );
};

export default App;</code></pre>
<pre><code class="language-jsx">const ButtonContainer = styled.TouchableOpacity`
  background-color: ${props =&gt;
    props.title === &#39;Hanbit&#39; ? props.theme.blue : props.theme.purple};</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[처음부터 배우는 리액트 네이티브]컴포넌트]]></title>
            <link>https://velog.io/@choi-ss-choco/%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</link>
            <guid>https://velog.io/@choi-ss-choco/%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</guid>
            <pubDate>Thu, 13 Nov 2025 18:27:47 GMT</pubDate>
            <description><![CDATA[<h1 id="컴포넌트">컴포넌트</h1>
<p>재사용할 수 있는 조립 블록. 화면에 나타나는 UI요소이다. </p>
<h2 id="jsx">JSX</h2>
<p>객체 생성과 함수 호출을 위한 문법적 편의를 제공하기 위해 만들어진 확장 기능이다. 가독성이 높고, 작성하기 쉬우며 중첩 구조를 잘 나타낼 수 있다.</p>
<h3 id="여러개의-요소를-표현할-경우-하나의-부모로-감싸야-한다">여러개의 요소를 표현할 경우 하나의 부모로 감싸야 한다.</h3>
<pre><code class="language-java">  return (
    &lt;View style={styles.container}&gt;
      &lt;Text&gt;Open up App.js to start working on your app!&lt;/Text&gt;
      &lt;StatusBar style=&quot;auto&quot; /&gt;
    &lt;/View&gt;
  );</code></pre>
<p>여러개의 요소를 반환하고 있어 View태그로 묶어주었다. View는 div태그와 비슷한 역할을 한다. View는 화면에 보이는 레이아웃 요소이다. 단순히 여러개의 컴포넌트를 반환하고 싶은 경우 Fragment컴포넌트를 사용한다. <Fragment></Fragment>로 사용해도 되고 &lt;&gt;&lt;/&gt;로 단축 문법을 사용해도 된다.</p>
<h3 id="내부에서-자바스크립트-변수를-전달하여-이용">내부에서 자바스크립트 변수를 전달하여 이용</h3>
<pre><code class="language-java">export default function App() {
  const name = &#39;Dayeon&#39;;
  return (
    &lt;View style={styles.container}&gt;
      &lt;Text style={styles.text}&gt;My name is {name}&lt;/Text&gt;
      &lt;StatusBar style=&quot;auto&quot; /&gt;
    &lt;/View&gt;
  );
}</code></pre>
<h3 id="자바스크립트-조건문">자바스크립트 조건문</h3>
<p>조건에 따라 나타나는 요소를 다르게 하고 싶을 때 JSX에서 조건문을 사용할 수 있다. 다만 제약 조건이 있어 밖에서 조건에 따른 값을 설정하고 JSX내부에서는 간단하게 작성하는 것이 깔끔하다.</p>
<ul>
<li><p>if문</p>
<pre><code class="language-java">  const name = &#39;Dayeon&#39;;
  return (
    &lt;View style={styles.container}&gt;
      &lt;Text style={styles.text}&gt;
        {(() =&gt; {
          if (name === &#39;Hanbit&#39;) return &#39;My name is Hanbit&#39;;
          else if (name === &#39;Dayeon&#39;) return &#39;My name is Dayeon&#39;;
          else return &#39;My name is React Native&#39;;
        })()}
      &lt;/Text&gt;
      &lt;StatusBar style=&quot;auto&quot; /&gt;
    &lt;/View&gt;
  );</code></pre>
</li>
<li><p>삼항연산자</p>
<pre><code class="language-java">  export default function App() {
    const name = &#39;Dayeon&#39;;
    return (
      &lt;View style={styles.container}&gt;
        &lt;Text style={styles.text}&gt;
          My name is {name === &#39;Dayeon&#39; ? &#39;Dayeon Choi&#39; : &#39;React Native&#39;}
        &lt;/Text&gt;
        &lt;StatusBar style=&quot;auto&quot; /&gt;
      &lt;/View&gt;
    );
  }</code></pre>
</li>
<li><p>AND, OR연산자</p>
<pre><code class="language-java">  export default function App() {
    const name = &#39;Dayeon&#39;;
    return (
      &lt;View style={styles.container}&gt;
        {name === &#39;Dayeon&#39; &amp;&amp; (
          &lt;Text style={styles.text}&gt;My name is Dayeon&lt;/Text&gt;
        )}
        {name !== &#39;Dayeon&#39; &amp;&amp; (
          &lt;Text style={styles.text}&gt;My name is not Dayeon&lt;/Text&gt;
        )}
        &lt;StatusBar style=&quot;auto&quot; /&gt;
      &lt;/View&gt;
    );
  }</code></pre>
</li>
<li><p>null과 undefined</p>
<p>  null은 허용하지만 undefined는 허용하지 않는다</p>
</li>
<li><p>주석</p>
<p>  JSX의 주석은 {/* <em>/}을 사용한다. 단, 태그 안에서는 // 나 /</em> */를 사용한다.</p>
</li>
<li><p>스타일링</p>
<p>  HTML과 달리 객체 형태로 입력해야 한다. 그리고 하이픈으로 연결된 이름은 카멜표기법으로 바꾸어 작성해야한다.</p>
<pre><code class="language-java">  const App = () =&gt; {
    return (
      &lt;View
        style={{
          flex: 1,
          backgroundColor: &#39;#fff&#39;,
          alignItems: &#39;center&#39;,
          justifyContent: &#39;center&#39;,
        }}
      &gt;
      ‹Text&gt;Open up App.js to start working on your app!&lt;/Text&gt;
      &lt;/View&gt;
    );
  };</code></pre>
</li>
</ul>
<h2 id="컴포넌트-1">컴포넌트</h2>
<p><a href="https://reactnative.dev/docs/components-and-apis">Core Components and APIs · React Native</a></p>
<p>위 공식 문서에서 다양한 내장 컴포넌트를 확인할 수 있다. </p>
<h3 id="button-컴포넌트-예시">Button 컴포넌트 예시</h3>
<pre><code class="language-java">&lt;View&gt;
  &lt;Text style={{ fontSize: 30, marginBottom: 10 }}&gt;Button Component&lt;/Text&gt;
  &lt;Button title=&quot;Button&quot; onPress={() =&gt; alert(&#39;Click !!!&#39;)} /&gt;
&lt;/View&gt;</code></pre>
<ul>
<li>title 속성: 버튼 내부에 출력되는 텍스트</li>
<li>onPress 속성: 버튼이 눌렸을 때 호출되는 함수</li>
<li>color: ios에서는 텍스트 색상, android에서는 바탕색</li>
</ul>
<p><em>이전에 Flutter를 할 때에는 모든 플랫폼에 동일하게 적용되었었는데, RN은 아니라고 하여 그 이유를 찾아보았다. Flutter는 자체적인 엔진(Skia)을 사용하지만 RN은 네이티브의 위젯을 가져와 사용해서 OS별로 다르게 나올 수 있다고 한다.</em></p>
<h3 id="커스텀-컴포넌트-만들기">커스텀 컴포넌트 만들기</h3>
<p>여러 내장 컴포넌트를 조합하여 새로운 컴포넌트를 만들 수 있다.</p>
<pre><code class="language-java">import React from &#39;react&#39;;
/*리액트를 불러옴*/
import { TouchableOpacity, Text } from &#39;react-native&#39;;
/*RN에서 제공하는 TouchableOpacity, Text컴포넌트 추가*/
const MyButton = () =&gt; {
  return (
    &lt;TouchableOpacity/*클릭에 대해 상호작용 할 수 있도록 함*/
      onPress={() =&gt; alert(&#39;Click !!!&#39;)}
    &gt;
      &lt;Text style={{ fontSize: 24 }}&gt;My Button&lt;/Text&gt;
    &lt;/TouchableOpacity&gt;
  );
};</code></pre>
<h2 id="props와-state">props와 state</h2>
<h3 id="props">props</h3>
<p>properties를 줄인 표현으로 부모 컴포넌트로부터 전달된 속성값 혹은 상속받은 속성값을 말한다. 부모로부터 받은 props를 사용할수는 있지만 변경은 불가능하다.</p>
<pre><code class="language-java">&lt;MyButton title=&quot;Button&quot; /&gt;
/*MyButton 컴포넌트에게 title이라는 이름의 데이터를 넘긴다*/</code></pre>
<pre><code class="language-java">const MyButton = props =&gt; {
  return (
    &lt;TouchableOpacity&gt;
      &lt;Text style={{ color: &#39;white&#39;, fontSize: 24 }}&gt;
        {props.title} /*props를 전달받아 출력*/
      &lt;/Text&gt;
    &lt;/TouchableOpacity&gt;
  );
};</code></pre>
<p>컴포넌트 태그 사이에 값을 입력하여 전달할 수도 있다</p>
<pre><code class="language-java">&lt;MyButton title=&quot;Button&quot;&gt;Children Props&lt;/MyButton&gt;</code></pre>
<pre><code class="language-java">const MyButton = props =&gt; {
  return (
    &lt;TouchableOpacity&gt;
      &lt;Text style={{ color: &#39;white&#39;, fontSize: 24 }}&gt;
        {props.children || props.title}
      &lt;/Text&gt;
    &lt;/TouchableOpacity&gt;
  );
};</code></pre>
<p>태그 사이로 전달된 값은 children으로 전달된다. props에 children이 있다면 title보다 우선시된다. </p>
<h3 id="defaultprops">defaultProps</h3>
<pre><code class="language-java">MyButton.defaultProps = {
  title: &#39;Button&#39;,
};</code></pre>
<p>기본값을 자식 컴포넌트에서 지정할 수 있다.</p>
<h3 id="proptypes">propTypes</h3>
<p>잘못된 props가 전달되었을 때 PropTypes를 사용한다.</p>
<pre><code class="language-java">import PropTypes from &#39;prop-types&#39;;

...

MyButton.propTypes = {
  title: PropTypes.number.isRequired,
};</code></pre>
<p>propsType을 number로 지정해주었다. isRequired를 붙여주면 필수 전달 여부를 지정할 수 있다. propsType에는 이 외에도 string, func, object, array등이 있다.</p>
<h3 id="state">state</h3>
<p>컴포넌트 내부에서 생성되고 값을 변경할 수 있으며 컴포넌트 상태를 관리한다. 상태가 변하면 컴포넌트는 리렌더링한다.</p>
<ul>
<li><p>useState</p>
<p>  함수형 컴포넌트에서 상태를 관리할 수 있도록 해준다.</p>
<pre><code class="language-java">  import React, { useState } from &#39;react&#39;;
  import { View, Text } from &#39;react-native&#39;;
  import MyButton from &#39;./MyButton&#39;;

  const Counter = () =&gt; {
    const [count, setCount] = useState(0);
    /*상태 관리 변수와 변수를 변경할 수 있는 세터 함수를 배열로 반환, 0으로 초깃값 설정*/
    return (
      &lt;View style={{ alignItems: &#39;center&#39; }}&gt;
        &lt;MyButton
          title=&quot;+&quot;
          onPress={() =&gt; {
            setCount(count + 1); /*버튼을 누르면 count +1*/
          }
        /&gt;
        &lt;MyButton
          title=&quot;-&quot;
          onPress={() =&gt; {
            setCount(count - 1); /*버튼을 누르면 count -1*/
          }
        /&gt;
      &lt;/View&gt;
    );
  };

  export default Counter;</code></pre>
<p>  useState는 여러개일 수 있다. 관리해야 하는 상태의 수만큼 useState를 사용한다.</p>
</li>
</ul>
<blockquote>
<p>React Native = 선언적(Declarative) UI + Virtual DOM 기반<br>   → 상태가 변하면 React가 “무엇을 다시 그릴지” 판단
   → 개발자는 신경 안 써도 됨</p>
</blockquote>
<blockquote>
<p>Flutter = 자체 렌더링 엔진 + 위젯 트리 직접 재구성
     → 업데이트할 시점을 개발자가 알려줘야 함
    → 대신 성능이 매우 빠르고 일관적임</p>
</blockquote>
<h2 id="이벤트">이벤트</h2>
<h3 id="press이벤트">press이벤트</h3>
<p>onClick이벤트와 비슷한 이벤트이다. TouchableOpacity 컴포넌트에서 설정할 수 있는 press 이벤트의 종류는 4가지이다.</p>
<blockquote>
<ul>
<li>onPressln: 터치가 시작될 때 항상 호출</li>
</ul>
</blockquote>
<ul>
<li>onPressOut: 터치가 해제될 때 항상 호출</li>
<li>onPress: 터치가 해제될 때 onPressOut 이후 호출</li>
<li>onLongPress: 터치가 일정 시간 이상 지속되면 호출</li>
</ul>
<pre><code class="language-java">import React from &#39;react&#39;;
import { TouchableOpacity, Text } from &#39;react-native&#39;;

const EventButton = () =&gt; {
  const _onPressIn = () =&gt; console.log(&#39;Press In !!!\n&#39;);
  const _onPressOut = () =&gt; console.log(&#39;Press Out !!!\n&#39;);
  const _onPress = () =&gt; console.log(&#39;Press !!!\n&#39;);
  const _onLongPress = () =&gt; console.log(&#39;Long Press !!!\n&#39;);

  return (
    &lt;TouchableOpacity
      style={{
        backgroundColor: &#39;#f1c40f&#39;,
        padding: 16,
        margin: 10,
        borderRadius: 8,
      }}
      onPressIn={_onPressIn}
      onLongPress={_onLongPress}
      onPressOut={_onPressOut}
      onPress={_onPress}
      delayLongPress={3000}/*onLongPress호출 시간 조절*/
    &gt;
      &lt;Text style={{ color: &#39;white&#39;, fontSize: 24 }}&gt;Press&lt;/Text&gt;
    &lt;/TouchableOpacity&gt;
  );
};

export default EventButton;</code></pre>
<p>onPress와 onLongPress는 사용자가 클릭하는 시간에 따라 둘 중에 하나만 호출된다.</p>
<h3 id="change-이벤트">change 이벤트</h3>
<pre><code class="language-java">import React, { useState } from &#39;react&#39;;
import { View, Text, TextInput } from &#39;react-native&#39;;

const EventInput = () =&gt; {
  const [text, setText] = useState(&#39;&#39;);
  const _onChange = event =&gt; setText(event.nativeEvent.text);

  return (
    &lt;View&gt;
      &lt;Text style={{ margin: 10, fontSize: 30 }}&gt;text: {text}&lt;/Text&gt;
      &lt;TextInput
        style={{ borderWidth: 1, padding: 10, fontSize: 20 }}
        placeholder=&quot;Enter a text...&quot;
        onChange={_onChange}/*TextInput컴포넌트에 입력된 텍스트가 변경될 때 호출*/
      /&gt;
    &lt;/View&gt;
  );
};

export default EventInput;</code></pre>
<p>위와 같이 텍스트만 변화될 경우 onChangeText를 사용하면 더 간편하다. 컴포넌트의 텍스트가 변경되었을 때 변경된 텍스트의 문자열만 인수로 전달하며 호출된다.</p>
<h3 id="pressable컴포넌트">Pressable컴포넌트</h3>
<p>기존의 TouchableOpacity 컴포넌트를 대체하는 Pressable 컴포넌트이다. 기존의 컴포넌트보다 더 다양한 기능을 제공한다. </p>
<pre><code class="language-java">import React from &#39;react&#39;;
import { View, Text, Pressable } from &#39;react-native&#39;;

const Button = (props) =&gt; {
  return (
    &lt;Pressable
      style={{ padding: 10, backgroundColor: &#39;#1abc9c&#39; }}
      onPressIn={() =&gt; console.log(&#39;Press In&#39;)}
      onPressOut={() =&gt; console.log(&#39;Press Out&#39;)}
      onPress={() =&gt; console.log(&#39;Press&#39;)}
      onLongPress={() =&gt; console.log(&#39;Long Press&#39;)}
      delayLongPress={3000}
      pressRetentionOffset={{ bottom: 50, left: 50, right: 50, top: 50 }}
      hitSlop={50}
    &gt;
      &lt;Text style={{ padding: 10, fontSize: 30 }}&gt;{props.title}&lt;/Text&gt;
    &lt;/Pressable&gt;
  );
};

const App = () =&gt; {
  return (
    &lt;View
      style={{
        flex: 1,
        justifyContent: &#39;center&#39;,
        backgroundColor: &#39;#fff&#39;,
        alignItems: &#39;center&#39;,
      }}&gt;
      &lt;Button title=&quot;Pressable&quot; /&gt;
    &lt;/View&gt;
  );
};

export default App;</code></pre>
<p>HitRect와 PressRect를 이용하여 이벤트 발생 범위를 조절할 수 있다. hitSlop값에 따라 PressRect의 범위가 달라진다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스 - CHATER 2. 관계 데이터 모델 (1)]]></title>
            <link>https://velog.io/@choi-ss-choco/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-CHATER-2.-%EA%B4%80%EA%B3%84-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%AA%A8%EB%8D%B8-1</link>
            <guid>https://velog.io/@choi-ss-choco/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-CHATER-2.-%EA%B4%80%EA%B3%84-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%AA%A8%EB%8D%B8-1</guid>
            <pubDate>Sun, 27 Mar 2022 13:41:35 GMT</pubDate>
            <description><![CDATA[<h2 id="관계-데이터-모델의-개념">관계 데이터 모델의 개념</h2>
<h4 id="릴레이션의-개념">릴레이션의 개념</h4>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/57e4775b-6d7b-4d74-8f6c-515f61ebe2b6/image.png" alt="">
<strong>릴레이션(relation)</strong>은 행과 열로 구성된 테이블을 말한다. relation의 사전적 정의는 &#39;관계&#39;이지만 뒤에 나오는 relationship과의 충돌을 피하기 위해 &#39;관계&#39;라고 하지 않는다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/878461d3-e726-4d77-88a0-e2e130e8c607/image.png" alt=""></p>
<p>relation은 집합에서 나온 개념이다. 위의 도서 relation의 경우 4개의 집합으로 이루어져있으며 각 집합은 다음과 같다.</p>
<p>도서번호 = {1, 2, 3, 4}
도서이름 = {축구의 역사, 축구아는 여자, 축구의 이해, 골프 바이블, 피겨 교본}
출판사 = {굿스포츠, 나무수, 대한미디어
가격 = {7000, 13000, 22000, 35000, 8000}</p>
<p>위 표에서 첫번째 행의 경우 네 개의 집합에서 각각의 원소를 하나씩 선택하여 만들어진 것으로 이 원소들이 relationship을 맺고 있다. </p>
<p>relationship에는 relation내의 관계와 relation간의 관계가 있다. </p>
<h4 id="릴레이션-스키마와-인스턴스">릴레이션 스키마와 인스턴스</h4>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/f7f73c85-bc7b-472b-a761-d77e0b30ae69/image.png" alt=""></p>
<p>relation은 schme와 instance로 이루어진다. </p>
<ul>
<li><p><strong>스키마(schme)</strong>
관계 데이터베이스의 릴레이션이 어떻게 구성되는지, 어떤 정보를 담고 있는지에 대한 기본적인 구조를 정의한다.</p>
</li>
<li><p><strong>인스턴스(instance)</strong>
정의된 스키마에 따라 테이블에 실제로 저장되는 데이터의 집합이다. </p>
</li>
</ul>
<p>위의 도서 릴레이션에서, 열(column)은 속성(attribute)라고도 하며 개수를 표현할 땐 차수(dgree)라는 표현을 사용한다. 속성이 가질 수 있는 값의 집합을 도메인(domain)이라고 한다.
행(row)은 튜플(tuple)이라고도 하며 수를 표현할 땐 카디널리티(cadinality)라는 표현을 사용한다. </p>
<h4 id="릴레이션의--특징">릴레이션의  특징</h4>
<ul>
<li>속성은 단일 값을 가진다. </li>
<li>속성은 서로 다른 이름을 가진다.</li>
<li>한 속성의 값은 모두 같은 도메인을 가진다.</li>
<li>한 속성의 값은 모두 같은 도메인 값을 가진다.</li>
<li>속성의 순서는 상관이 없다.</li>
<li>릴레이션 내의 중복된 투플은 허용하지 않는다.</li>
<li>투플의 순서는 상관없다.</li>
</ul>
<h4 id="관계-데이터-모델">관계 데이터 모델</h4>
<p> 관계 데이터 모델은 데이터를 2차원 테이블 형태인 릴레이션으로 표현하며 릴레이션에 대한 제약조건과 관계 연산을 위한 관계대수를 정의한다. 
 관계 데이터 모델을 컴퓨터 시스템에 구현한 것을 관계 데이터베이스 시스템이라고 한다. 관계 데이터베이스 시스템은 관계 데이터 모델에 기초하여 SQL로 수행하고, 릴레이션에 대한 제약조건을 SQL을 기반으로 구현된다.</p>
<h2 id="키key">키(key)</h2>
<p> 특정 튜플을 식별할 때 사용하는 속성(attribute) 혹은 속성의 집합이다. 키가 되는 속성은 반드시 값이 달라서 튜플들을 서로 구별할 수 있어야 한다. 키는 릴레이션간 관계를 맺는 데도 사용한다.</p>
<ul>
<li><p><strong>슈퍼키(super key)</strong>
튜플을 유일하게 식별할 수 있는 하나의 속성 혹은 속성의 집합.</p>
</li>
<li><p><strong>후보키(candidate key)</strong>
튜플을 유잃게 식별할 수 있는 속성의 최소 집합. </p>
</li>
<li><p><strong>복합키(composite key)</strong>
두 개 이상의 속성으로 이루어진 키.</p>
</li>
<li><p><strong>기본키(primary)</strong>
여러 후보키 중 하나를 선정하여 대표로 삼는 키. 
기본키를 선정할 때엔 다음과 같은 고려사항이 있다.</p>
</li>
</ul>
<ol>
<li><p>릴레이션 내 튜플을 식별할 수 있는 고유한 값을 가져야 한다. </p>
</li>
<li><p>NULL값는 허용하지 않는다.</p>
</li>
<li><p>키값의 변동이 일어나지 않아야 한다.</p>
</li>
<li><p>최대한 적은 수의 속성을 가진 것이어야 한다.</p>
</li>
<li><p>향후 키를 사용하는 데 있어서 문제 발생 소지가 없어야 한다.
릴레이션 스키마를 표현할 때 기본키는 밑줄을 그어 표시한다.</p>
<p>데이터베이스에 저장된 데이터는 결함이 없어야 하며 질의에 대해 신뢰성 있는 답을 제공해야 한다.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA 1. 자바 시작]]></title>
            <link>https://velog.io/@choi-ss-choco/JAVA-1.-%EC%9E%90%EB%B0%94-%EC%8B%9C%EC%9E%91</link>
            <guid>https://velog.io/@choi-ss-choco/JAVA-1.-%EC%9E%90%EB%B0%94-%EC%8B%9C%EC%9E%91</guid>
            <pubDate>Wed, 23 Mar 2022 05:34:08 GMT</pubDate>
            <description><![CDATA[<h2 id="java의-시작">JAVA의 시작</h2>
<p> JAVA는 제임스 고블링에 의해 개발되었다. 기존의 불편한 방식을 없애기 위해 플랫폼으로부터 독립적이며 메모리 사용량이 적은 새로운 언어를 만들기를 희망했고, 그렇게 탄생된 것이 JAVA이다. </p>
<p> JAVA는 플랫폼 종속성을 극복하여 C, C++와는 다르게 운영체제나 하드웨어에 상관없이 자바 가상 기계(JVM - Java Virtual Machine)가 있으면 어디서나 동일한 실행 결과를 기대할 수 있다. 이를 WORA(Write Once Run Anywhere)라고 한다.</p>
<p>  자바 프로그램도 여러 소스파일(.java)로 나누어 작성하며 자바 컴파일러는 각 소스 파일을 컴파일하여 클래스 파일(.class)을 생성한다.하지만 자바에서는 이들을 하나의 실행파일(.exe)로 묶는 링크의 과정이 없다. 이런 과정으로 자바로 작성 된 프로그램은 적은 메모리를 가진 컴퓨터에서도 실행될 수 있다.</p>
<h2 id="jdk와-jre">JDK와 JRE</h2>
<p>  JDK(Java Development Kit)는 자바 개발자에게 무료로 배포하는 소프트웨어로 개발 도구와 JRE(Java Runtime Enviroment)로 구성된다. 
  <img src="https://images.velog.io/images/choi-ss-choco/post/31bc3a48-49b6-4c4b-b284-235fefe8d84f/image.png" alt=""></p>
<p>  <a href="https://www.oracle.com/java/technologies/downloads/">https://www.oracle.com/java/technologies/downloads/</a> 에서 다운받을 수 있다.</p>
<h2 id="자바-api">자바 API</h2>
<p>  자바 API(Application Programing Interface)는 개발자들이 사용하도록 미리 만들어놓은 유용한 자바 클래스들의 집합이다. API에 대한 설명은 <a href="https://docs.oracle.com/en/java/javase/17/docs/api/index.html">https://docs.oracle.com/en/java/javase/17/docs/api/index.html</a> 에서 볼 수 있다. </p>
<h2 id="자바-ide">자바 IDE</h2>
<p>  IDE는 통합 개발 환경이다. 나는 eclipse를 사용할 예정이다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스 - CHAPTER 1. 데이터베이스 시스템]]></title>
            <link>https://velog.io/@choi-ss-choco/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-CHAPTER-1.-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%8B%9C%EC%8A%A4%ED%85%9C</link>
            <guid>https://velog.io/@choi-ss-choco/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-CHAPTER-1.-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%8B%9C%EC%8A%A4%ED%85%9C</guid>
            <pubDate>Mon, 14 Mar 2022 11:26:41 GMT</pubDate>
            <description><![CDATA[<h3 id="데이터베이스와-데이터베이스-시스템">데이터베이스와 데이터베이스 시스템</h3>
<h4 id="데이터베이스의-개념-및-특징">데이터베이스의 개념 및 특징</h4>
<blockquote>
<p><strong>데이터(data)</strong>
관찰의 결과로 나타난 정량적 혹은 정성적인 실제 값
<strong>정보(information)</strong>
데이터에 의미를 부여한 것
<strong>지식(knowledge)</strong>&#39;
사물이나 현상에 대한 이해</p>
</blockquote>
<blockquote>
<h3 id="데이터베이스database">데이터베이스(database)</h3>
<p>조직에 필요한 정보를 얻기 위해 논리적으로 연관된 데이터를 모아 구조적으로 통합 해놓은 것</p>
</blockquote>
<ul>
<li><strong>통합된 데이터(intergrated data)</strong>
데이터를 통합하는 개념으로 각자 사용하던 데이터의 중복을 최소화하여 중복으로 인한 데이터 불일치 현상을 제거</li>
<li><strong>저장된 데이터(stored data)</strong>
문서로 보관된 데이터가 아니라 디스크, 테이프 같은 컴퓨터 저장장치에 저장된 데이터를 의미</li>
<li><strong>운영 데이터(operational data)</strong>
조직의 목적을 위해 사용되는 데이터를 의미. 즉 업무를 위한 검색을 할 목적으로 저장된 데이터</li>
<li><strong>공용데이터(shared data)</strong>
한 사람 또는 한 업무를 위해 사용되는 데이터가 아니라 공동으로 사용되는 데이터를 의미</li>
</ul>
<h4 id="데이터베이스-시스템의-구성">데이터베이스 시스템의 구성</h4>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/9dc611c3-c432-411d-97e9-832967f0dfce/image.png" alt=""></p>
<p>데이터베이스는 보통 하드디스크에 저장이 되어있는 데이터의 모임이라고 생각하면 된다. </p>
<ul>
<li><p><strong>DBMS(Data Base Management System)</strong>
데이터베이스 관리시스템이다. 이 소프트웨어를 작동시켜야 데이터베이스를 다룰 수 있다.
<img src="https://images.velog.io/images/choi-ss-choco/post/68d058f0-ed07-4343-afeb-06b04b200029/image.png" alt=""></p>
</li>
<li><p><strong>데이터베이스 사용자</strong>
DBMS를 사용하는 user는 4가지로 나눌 수 있으며 이들은 직접 데이터베이스를 다루는 것이 아니라 응용프로그램(application)을 이용하여 데이터에 접근한다. 
<img src="https://images.velog.io/images/choi-ss-choco/post/f65f3848-5c26-44c8-a26d-921fb2aa7970/image.png" alt=""></p>
</li>
<li><p><strong>데이터베이스 언어</strong>
데이터베이스 시스템은 데이터베이스 전용 언어인 SQL을 사용한다.  </p>
</li>
</ul>
<h4 id="3단계-데이터베이스-구조">3단계 데이터베이스 구조</h4>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/821fac34-7d4c-4a71-9074-d5e08a1ef750/image.png" alt=""></p>
<p>3단계 데이터베이스 구조는 외부단계, 개념단계, 내부단계로 나뉜다. <strong>스키마(schema)</strong>는 조직이나 구조를 의미한다. </p>
<ul>
<li><p><strong>외부단계</strong>
일반 사용자나 응용프로그래머가 접근하는 계층으로 데이터베이스 전체 중에서 하나의 논리적인 부분을 의미한다. 서브 스키마(subschema)라고도 부른다.</p>
</li>
<li><p><strong>개념단계</strong>
전체 데이터베이스의 정의를 의미한다. 통합 조직별로 하나만 존재하며 DBA가 관리한다. </p>
</li>
<li><p><strong>내부단계</strong> 
물리적인 저장장치에 데이터베이스가 실제로 저장되는 방법의 표현이다.</p>
</li>
</ul>
<p>DBMS는 <strong>매핑(mapping)</strong>을 통하여 각 단계간 대응 관계를 정의한다. </p>
<ul>
<li><p><strong>외부/개념 매핑</strong>
사용자의 외부 스키마와 개념 스키마 간의 매핑으로 외부 스키마의 데이터가 개념 스키마의 어떤 부분에 해당하는지 대응시킨다.</p>
</li>
<li><p><strong>개념/내부 매핑</strong> 
개념 스키마의 데이터가 내부 스키마의 물리적 장치 어디에 어떤 방법으로 저장되는지 대응시킨다. </p>
</li>
</ul>
<h4 id="데이터-독립성">데이터 독립성</h4>
<p>데이터 독립성은 하위 단계의 내용을 추상화하여 상위 단계에 그 세부사항을 숨김으로써 한 단계 내의 변경에 대해 다른 단계와의 상호 간섭이 없도록 하는 것이다.</p>
<ul>
<li><p><strong>논리적 데이터 독립성(logical data independence)</strong>
외부 단계와 개념 단계 사이의 독립성. 개념 스키마가 변경되어도 외부 스키마에는 영향을 미치지 않도록 지원한다.</p>
</li>
<li><p><strong>물리적 데이터 독립성(phygical data independence)</strong>
개념 단계와 내부 단계 사이의 독립성. 내부 스키마가 변경되어도 개념 스키마에는 영향을 미치지 않도록 지원한다. logical data independence보다 구현하기 쉽다. </p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python 9. 클래스와 객체 지향 프로그래밍]]></title>
            <link>https://velog.io/@choi-ss-choco/Python-9.-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%99%80-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</link>
            <guid>https://velog.io/@choi-ss-choco/Python-9.-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%99%80-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</guid>
            <pubDate>Sat, 12 Mar 2022 02:29:31 GMT</pubDate>
            <description><![CDATA[<h2 id="class">class</h2>
<p>클래스는 속성/필드, 메소드로 구성된다.</p>
<ul>
<li>속성/필드(attribute/filed): 데이터 멤버, 변수 등</li>
<li>메소드(method): 클래스 내부의 함수</li>
</ul>
<p>클래스를 생성할 때엔 특수인자인 self를 사용한다. 다른 이름을 사용해도 되지만 self가 관례적이다. 자신을 참조하는 인자이다. 생성자는 객체 생성시 호출 되는 특수한 메소드로 앞 뒤에 언더바(_)가 2개씩 붙어있다.</p>
<p>객체(object)는 클래스의 개체(instance)이다. &#39;클래스&#39;라는 것은 일종의 타입, 형태이고 그 안에 속하는 것이 객체, 클래스의 개체이다.  </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/6296c9f7-43b0-48c9-93cc-997f90aa1534/image.png" alt=""></p>
<p>불필요한 정보는 숨기는 것이 좋다. 외부에서 접근할 수 없도록 하면 내가 직접 수정을 하더라도 외부에서는 영향을 받지 않기 때문이다. 이렇게 정보를 숨기는 것을 <strong>정보 은닉(information hiding)</strong> 이라고 한다. 일부만 열여두고 나머지는 숨기는 캡슐화(encapsulation)를 사용하여 재사용성을 높일 수 있다.</p>
<ul>
<li>공개(public): 외부에서도 접근 가능</li>
<li>비공개(private) member: 내부에서만 접근 가능
-&gt; 2개 이상의 &#39;_&#39;로 시작, 끝에는 1개 이하의 &#39;_&#39;를 사용하면 정보가 비공개된다. </li>
</ul>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/04f913ee-74c5-4130-b440-37176995ec34/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python 8. 예외 처리와 파일]]></title>
            <link>https://velog.io/@choi-ss-choco/Python-8.-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC%EC%99%80-%ED%8C%8C%EC%9D%BC</link>
            <guid>https://velog.io/@choi-ss-choco/Python-8.-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC%EC%99%80-%ED%8C%8C%EC%9D%BC</guid>
            <pubDate>Sat, 12 Mar 2022 02:29:12 GMT</pubDate>
            <description><![CDATA[<h2 id="예외처리">예외처리</h2>
<p>어떤 프로그래밍 언어에서 정해진 문법을 따르지 않는 명령이 입력되어 프로그램이 문제를 일으키는 것을 오류라고 한다. </p>
<h3 id="오류의-종류와-특징">오류의 종류와 특징</h3>
<p>많은 프로그램에서 오류를 수행 전에 찾기 어려운 경우가 있다. 파이썬 인터프리터에서 오류 정보를 제공하고 프로그램은 오류를 역추적하여 오류가 발생한 곳에서 오류의 종류를 출력한다. 다음은 오류의 종류이다.</p>
<ul>
<li>구문오류: print()함수에서 따옴표를 빼먹는 등의 오류. 문법오류라고도 한다.</li>
<li>값 오류</li>
<li>인덱스 오류</li>
<li>0으로 나누기 오류</li>
<li>파일 가져오기 오류</li>
</ul>
<h3 id="예외처리-1">예외처리</h3>
<p>예외란 구문상의 오류는 없으나 실행 도중에 만나게 되는 오류이다. 이런 오류를 다루는 기술을 예외처리기술이라고 한다.</p>
<p>이 때는 try-except문의 문법을 사용한다. try절에는 예외가 발생할 우려가 있는 코드를, except 절에는 오류가 발생했을 때 처리할 내용을 담는다.</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/02e89117-d276-48cc-9153-8f0081a64f06/image.png" alt=""></p>
<p>try-except문을 이용함으로써 프로그램은 갑작스럽게 종료되지 않고 사용자에게 대처할 수 있는 방법을 제시할 수 있으며 이전의 프로그램에 비해 안정적으로 작동한다. </p>
<p>만약 어떤 예외 상황에서 except가 실행되었는지 알고 싶을 경우 Exception as e 라는 문법을 통해 Exception 변수 e를 선언한 후 다음과 같이 e 값을 출력하면 오류의 종류를 알 수 있게 된다. </p>
<pre><code>except Exception as e:
    print(&#39;error:&#39;, e)
</code></pre><p>except다음에 오류 메세지를 적어주면 해당 오류가 발생했을 때 실행할 코드를 작성할 수 있다. 여러 except구문이 있으면 위에서부터 차례대로 검사한다. 파이썬 페이지에서 내장 예외의 클래스 계층 구조를 확인할 수 있다.</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/9ad2b6f3-0eb3-43a9-9f2b-49bf6b4a484a/image.png" alt=""></p>
<p>try-except-else문이 있다. 예외가 발생하지 않을 경우 else문을 사용하여 그 결과값을 출력한다. else문이 가장 마지막에 와야 한다.</p>
<p>try-except-finally문이 있다. finally는 예외의 발생 여부와 상관 없이 항상 실행한다. </p>
<p>raise를 이용하여 강제로 예외 발생이 가능하다. raise문에 의해서 Exception이 발생되어 파이썬 인터프리터 프로그램이 그 과정을 추적하였다는 출력이 뜬다.</p>
<h2 id="파일-입출력">파일 입출력</h2>
<p>파이썬에서 파일을 사용하기 위한 절차가 있다. 파일을 경로를 통해 열어서 사용목적에 따라 사용하고 마지막에는 꼭 파일을 닫아야 한다. 그 메모리를 시스템에서 사용할 수 있도록 반환해주는 것이다. </p>
<p>파일을 열 때는 open()이라는 함수를 사용하고 닫을 때는 close()라는 메소드를 사용한다. 파일을 열 때 경로를 설정하지 않았다면 파이썬 스크립트파일이 있는 위치의 파일을 연다. 그 뒤에 오픈모드를 설정하여 가져온다. </p>
<h3 id="파일-열기">파일 열기</h3>
<p> open함수는 파이썬에서 파일을 다루는 핵심 함수이다. 파일 이름과 파일 오픈 모드의 인자를 가질 수 있다. encoding이라는 인자를 통해 파일의 인코딩 형식을 지정 가능하다. 지정된 경로가 없을 경우 디폴트 경로는 파이썬 스크립트 파일이 있는 곳이 된다.</p>
<p> <img src="https://images.velog.io/images/choi-ss-choco/post/43104dfd-8f92-4fac-afff-0d3360a3f07b/image.png" alt=""></p>
<p> 오픈 모드들이다. 기본적으로 4가지가 제공되고 +를 이용하여 추가적으로 모드를 설정할 수 있다.</p>
<p> <img src="https://images.velog.io/images/choi-ss-choco/post/26d37ff4-e893-4811-8554-7bde9e0cf5b3/image.png" alt=""></p>
<p> 이진 파일을 다룰지 텍스트 파일을 다룰지 지정하는 모드이다. 디폴트값은 t이며 b는 이진 파일 형식으로 파일을 열거나 생성한다.</p>
<p> <img src="https://images.velog.io/images/choi-ss-choco/post/ea8b8caf-35cc-4527-bdf6-fb398ec1a487/image.png" alt=""></p>
<h3 id="파일-읽기">파일 읽기</h3>
<p>read()메소드를 통해서 파일을 읽어올 수 있다. 파일의 모든 문자열은 줄 바꿈에 관계없이 하나의 문자열로 반환된다. 인자로 수를 주면 그 수만큼의 문자를 읽어와서 반환한다.</p>
<p>readline()메소드는 줄별로 파일을 읽어올 수 있다. 우리 눈에는 보이지 않지만 줄바꿈이 일어났다면 그 뒤에는 줄바꿈문자가 포함되어있는 것이다. readline()함수는 그 줄바꿈문자까지 포함하여 읽어온다. print를 사용하여 출력한다면 readline과  print에 의해서 두 번 줄바꿈이 된다. 한 번만 줄바꿈을 하고 싶다면 end를 사용한다. 또는 rstrip()메소드를 사용하여 마지막에 나오는 문자를 삭제할 수 있다. </p>
<h3 id="파일-추가하기">파일 추가하기</h3>
<p>&#39;a&#39;모드를 사용한다. 추가모드로 이미 있는 파일 뒤에 새로운 내용을 입력한다. w모드는 기존의 내용을 초기화하고 내용을 삽입한다는 것과 차이가 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python. 7-1. 모듈과 활용]]></title>
            <link>https://velog.io/@choi-ss-choco/Python.-7-1.-%EB%AA%A8%EB%93%88%EA%B3%BC-%ED%99%9C%EC%9A%A9</link>
            <guid>https://velog.io/@choi-ss-choco/Python.-7-1.-%EB%AA%A8%EB%93%88%EA%B3%BC-%ED%99%9C%EC%9A%A9</guid>
            <pubDate>Sat, 13 Nov 2021 17:14:13 GMT</pubDate>
            <description><![CDATA[<h2 id="모듈">모듈</h2>
<p><strong>모듈</strong>이란 파이썬 함수나 변수 또는 클래스를 모아놓은 스크립트 파일이다. 파이썬은 수많은 개발자들에 의해서 개발된 많은 모듈이 있다. 만들어진 모듈을 가져올 때에는  <strong>&#39;import&#39;</strong> 와 함께 모듈 이름을 써 준다. 사용할 때에는 모듈 이름에 점을 찍은 후 모듈 안의 구성요소를 작성한다. 파이썬 설치와 함께 제공되는 모듈을 <strong>파이썬 표준 라이브러리</strong> 라고 한다. 날짜, 시간, 수학함수 등과 관련된 모듈이 있다. <strong>구글에 python documentation</strong>이라고 검색해보면 모듈을 모아둔 문서를 볼 수 있다.</p>
<p><strong>dir()함수</strong>는 모듈이 가진 클래스의 목록을 출력해준다. import로 모듈을 불러낸다음 사용해야한다.</p>
<p>매번 &quot;모듈이름.클래스이름.메소드이름&quot; 을 점 연산자로 구분해서 적는 것은 매우 번거롭다. as구문을 사용하여 모듈 이름을 입력하는 사람이 임의로 정한 별칭으로 간단하게 줄일 수 있다. 
--&gt; import 모듈이름 as 모듈의 별칭</p>
<p>&quot;모듈이름.클래스이름.메소드이름&quot; 방식의 호출을 간단하게 하는 방법으로 &quot;from 모듈import 클래스,함수&quot; 구문이 있다. 그 뒤로는 모듈 이름 또는 클래스 이름 없이 코드를 짤 수 있다. </p>
<h3 id="날짜와-시간-모듈-datetime">날짜와 시간 모듈 datetime</h3>
<p>날짜와 시간에 관한 기능을 제공하고 조작할 수 있는 모듈이다.</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/491b35f3-213d-4bed-9087-b134db463dac/image.png" alt=""></p>
<p>앞의 datetime은 모듈의 이름, 뒤는 클래스의 이름을 의미한다. now라는 메소드는 현재 시간을 알려주는 메소드이다. 시간은 객체의 형태로 반환된다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/b615a93c-e7d7-4706-a977-199827c09ebb/image.png" alt=""></p>
<p>date 클래스의 today()메소드는 현재 날짜를 반환하는 메소드이다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/67700917-70fe-49ca-bfb2-e0122a826a27/image.png" alt=""></p>
<p>datetime 클래스의 replace()메소드는 날짜나 시간 값을 변경할 수 있는 메소드이다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/ec1bb47a-aea1-44d7-a0c8-33100c08944c/image.png" alt=""></p>
<p>datetime모듈을 이용하여 크리스마스까지 남은 시간을 계산해보았다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/411ec1df-ef8f-4844-b65d-57d3ae51ffbf/image.png" alt="">
<img src="https://images.velog.io/images/choi-ss-choco/post/b1ed6252-efec-4865-b4bb-a01080072610/image.png" alt=""></p>
<p>timedelta 클래스는  +,- 연산을 이용하여 시간의 연산이 가능하다. 위의 사진은 timedelta에 들어가는 인자를 적은 표이다. </p>
<h3 id="time-모듈">time 모듈</h3>
<p>time모듈은 시간에 관련된 정보를 제공하는 모듈이다. 유닉스 시스템의 시작 시간은 1970년 1월 1일 0시 0분 0초 협정 세계시를 <strong>에폭</strong>이라고 한다. 유닉스 운영체제에서 표준으로 사용되는 시간체계는 <strong>에폭 시간 혹은 유닉스 시간</strong>이라고도 한다. </p>
<p>sfrttime()은 localtime()함수가 반환한 struct_time 이라는 형식의 튜플 값을 지정된 포맷의 문자열로 변환한다.</p>
<h3 id="math-모듈">math 모듈</h3>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python 6-2. 딕셔너리, 튜플, 집합]]></title>
            <link>https://velog.io/@choi-ss-choco/Python-7-1.-%EB%94%95%EC%85%94%EB%84%88%EB%A6%AC</link>
            <guid>https://velog.io/@choi-ss-choco/Python-7-1.-%EB%94%95%EC%85%94%EB%84%88%EB%A6%AC</guid>
            <pubDate>Sun, 07 Nov 2021 05:29:01 GMT</pubDate>
            <description><![CDATA[<h2 id="딕셔너리">딕셔너리</h2>
<p>딕셔너리는 키와 값을 쌍으로 갖는 자료형이다. 키를 이용하여 값을 참조하는 특성을 지니고 있다. 딕셔너리의 항목은 <strong>{[키]:[값], [키]:[값]}</strong> 의 형식으로 나타낸다. 리스트와는 다르게 중괄호를 사용한다는 점에 주의 해야 한다.</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/65f9a862-094b-4118-bbb3-07201bca7b7f/image.png" alt=""></p>
<p>딕셔너리를 만들고 참조하는 방법이다. 키와 값에 형의 제한은 없다. 다만 키의 값은 유일해야한다. 값은 중복이 허용된다. </p>
<h3 id="딕셔너리의-삽입과-삭제">딕셔너리의 삽입과 삭제</h3>
<p>딕셔너리에 새로운 항목을 삽입하려면 <strong>딕셔너리이름[키] = 값</strong> 을 입력한다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/bfd1aef7-5aeb-4851-a0ea-18b076ad642f/image.png" alt=""></p>
<p>항목에 키가 4이고 값이 99인 새로운 항목이 추가되었다. 2라는 키가 가진 항목이 중복되었으므로 앞의 입력은 무시되었다. 만약 이미 존재하는 키에 다른 값을 입력한다면 값이 변경된다.</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/bb6210ce-e869-4ea7-850a-a78e232b3ff3/image.png" alt=""></p>
<p>만약 어떤 항목을 <strong>삭제하고 싶다면 del키워드</strong> 다음에 삭제할 딕셔너리의 키값을 입력하면 된다. 그러나 존재하지 않는 항목을 삭제하면 KeyError오류가 발생한다. </p>
<h3 id="딕셔너리를-위한-함수와-연산">딕셔너리를 위한 함수와 연산</h3>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/d4842431-a88b-4962-92c6-db3b27c51a03/image.png" alt=""></p>
<ul>
<li>len(): 딕셔너리 내의 항목의 수를 얻기 위해 사용</li>
<li>in, not in: 키가 딕셔너리에 있는지 확인</li>
<li>==, != : 두 개의 딕셔너리가 동일한 항목을 가지고 있는지 확인</li>
</ul>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/de7e820e-1f92-431a-b4d0-a89530a994eb/image.png" alt=""></p>
<p>딕셔너리의 메소드이다. items 메소드를 통해 반환되는 항목들은 괄호 내에서 쉼표로 구분되어있다. 이것은 리스트나 튜플이 아닌 <strong>dict view object</strong>라는 특정 개체를 반환한다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/18a25ad7-c7d4-40fa-9204-cd39f6891f48/image.png" alt=""></p>
<p>이 개체는 반복이 가능하기 때문에 for문을 사용할 수 있다. 이것은 딕셔너리도 마찬가지이다. 여기서 주의할 점은 딕셔너리에서 값을 받는 <strong>n은 값이 아닌 키</strong>라는 점이다. 만약 값을 출력하고 싶다면 d.values를, 키와 값의 쌍을 받고 싶다면 d.items를 입력한다. 이 때 <strong>d.items를 입력 후 나온 결과값은 튜플</strong>의 형태이다.</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/b72b601d-3f18-4e58-8022-5d4c5332fe0b/image.png" alt=""></p>
<p>person의 항목에서 없는 항목을 가져오려 한다면 KeyError가 발생하고 프로그램이 종료되지만 <strong>get 메소드는 없는 항목을 가져오려고 한다면 None을 반환</strong>한다. 만약 어떤 키의 값이 None이라면 get 메소드를 사용하였을 때 당연히 None을 반환한다. 두 경우를 헷갈리지 않도록 주의해야한다. 두 경우를 구분하는 방법은 in 연산을 사용하는 것이다.</p>
<h3 id="리스트와-딕셔너리의-비교">리스트와 딕셔너리의 비교</h3>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/37d39b89-bae5-4cbd-b3f9-2875f34c3c34/image.png" alt=""></p>
<p>딕셔너리는 키의 값도 저장해야하기때문에 저장공간이 많이 필요하다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/ca56a0ee-e1c0-47f4-a0ba-c33d219a7214/image.png" alt=""></p>
<p>리스트에서는 앞의 값이 삭제되면 앞으로 당겨진다. 그러나 딕셔너리에서는 앞의 값이 삭제되어도 키와 값의 쌍은 변하지 않는다. 리스트에서는 정의된 순서가 의미가 있지만 딕셔너리에서는 의미가 없다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/65f67638-122d-480b-8a32-216693b83139/image.png" alt=""></p>
<p>리스트는 대괄호를, 딕셔너리는 중괄호를 사용한다. 값에 접근하려면 리스트는 인덱스 번호를, 딕셔너리는 키를 이용한다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/ef538063-5f42-4689-91d6-59c99c0910f8/image.png" alt=""></p>
<h2 id="튜플">튜플</h2>
<p>튜플은 여러 개의 요소를 가지는 컬렉션 자료형이다. (1, 10, 30)과 같이 소괄호를 사용하며 쉼표로 구분을 한다. 튜플도 리스트와 마찬가지로 인덱스를 사용한다. 그러나 리스트와는 달리 한 번 정해진 요소의 순서를 바꿀 수 없다. 또한 튜플 내부의 객체를 변경하거나 삭제하는 것도 불가능하다는 <strong>교환불가능(immutable)</strong> 속성을 가지고 있다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/5fce564f-25ec-4c8b-b34e-74b4728906c7/image.png" alt=""></p>
<p>튜플을 생성하는 여러가지 방법이다. 리스트와 비슷한 부분이 많다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/f15f7eac-516a-481d-a7a0-2be35dc88941/image.png" alt=""></p>
<p>튜플은 내부의 값을 변경할 수 없으므로 개별 항목에 대한 할당 연산은 오류를 발생시킨다.</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/a45348f4-9ef8-4b7f-b7ec-dd2909cc04e5/image.png" alt=""></p>
<p><strong>패킹</strong>은 하나의 변수에 여러 개의 값을 넣는 것을 의미하며, <strong>언패킹</strong>은 그 반대로 패킹된 변수가 있으면 여기에서 여러 개의 값을 꺼내오는 것을 의미한다. </p>
<p>a,b = b,a라는 한 줄을 이용하여 두 변수의 값을 바꾸는 <strong>스왑</strong>을 할 수 있다. 효율성이 좋고 코드가 직관적이다. </p>
<h3 id="튜플의-연산">튜플의 연산</h3>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/d8de10f6-6705-4750-84cf-cc80d4cb9cbc/image.png" alt=""></p>
<p>튜플 역시 +와 *연산을 사용할 수 있다. +현산은 피연산 튜플의 원소들을 서로 연결하는 기능을 가지고 있다. *은 튜플과 정수 사이의 연산이 되어야한다.</p>
<h3 id="튜플의-메소드">튜플의 메소드</h3>
<p>튜플에서 사용되는 메소드는 다음과 같다.</p>
<ul>
<li>count(x) : 튜플 내에 특정 값 x가 몇 개 있는가를 알려준다.</li>
<li>index(x) : 튜플 내의 특정 값 x의 위치를 알려준다. </li>
</ul>
<p>튜플 내의 항목을 변경하고 싶다면 튜플을 list()함수를 사용하여 리스트로 변경한 뒤 이 리스트의 값을 변경시킨 후, tuple()함수를 사용하여 다시 리스트를 튜플로 만드는 방법을 사용한다. </p>
<h2 id="집합">집합</h2>
<p>집합은 순서가 없는 자료형이다. 중복이 허용되지 않으므로 인덱스를 사용하지 못한다. 동일한 값을 가지는 항목의 중복이 허용되지 않는다. 교집합, 합집합, 차집합 등의 집합 연산 수행이 가능하다. 집합은 중괄호를 이용하여 나타낸다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/11397f42-2fcf-47be-9ec1-5b8cfaa675eb/image.png" alt=""></p>
<p>집합을 만드는 여러가지 방법이다. 빈 집합을 만들 때 <strong>{}라고 한다면 이것은 딕셔너리</strong>를 의미하므로 주의해야 한다.</p>
<h3 id="집합의-메소드">집합의 메소드</h3>
<p>집합의 메소드는 다음과 같다
<img src="https://images.velog.io/images/choi-ss-choco/post/0e5f8a97-e6b6-41a7-836a-2cf9100675be/image.png" alt=""></p>
<p>add, discard, clear는 집합이 변경 되는 것, union, difference, intersection, symmetric_difference는 새로운 집합을 반환하는 것이다. </p>
<p>교집합은 &amp;, 합집합은|, 차집합은 -, 대칭 차집합은 ^연산을 사용한다.</p>
<h4 id="곱집합데카르트-곱">곱집합(데카르트 곱)</h4>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/565fabad-f0df-41df-8e71-0af5e3782af5/image.png" alt=""></p>
<p>곱집합이란 집합 A의 모든 원소 a와 집합 B의 모든 원소 b를 이용해 만들 수 있는 튜플 (a,b)의 집합이다. 곱집합은 AXB로 표현한다.</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/2d713b5e-d53c-48ed-afe1-60262e7e9a9e/image.png" alt=""></p>
<p>*연산자는 패킹 기능을 하는 연산자라고 할 수 있다. 함수에서 입력 받을 때 *이면 튜플, 즉 위치인자를 **이면 딕셔너리 형태로 키워드 인자를 묶어서 전달한다.</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/fd5e9d34-0b7a-4ab9-b49f-b35cf55e559a/image.png" alt="">
*, ** 연산자가 인자를 받는 곳이 아닌 다른 곳에서 쓰인다면 언패킹의 의미를 갖고 있다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/775f8acc-9b16-409c-bea1-92bc16819d9c/image.png" alt="">
첫번째 줄에서 c는 3, 4, 5, 6이 <strong>패킹된 리스트</strong>의 형태로 전달이 된다.</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/60cb410b-cc29-416c-8f03-8376bd2a04d8/image.png" alt=""></p>
<p>위 내용을 이용하여 곱집합을 출력하는 내용이다.</p>
<h3 id="zip함수를-이용한-집적화">zip()함수를 이용한 집적화</h3>
<p>리스트, 딕셔너리, 집합, 튜플과 같은 자료형은 하나씩 꺼내어 반복하여 처리할 수 있는 자료형이므로 반복가능자료형이라고 한다. 반복가능 자료형 여러 개 인자로 넘겨주면, 이들을 합쳐서 튜플 반복자를 반환하는 함수이다. zip()함수는 다음과 같이 여러 개의 반복가능 자료형을 받을 수 있다. 이를 집적화라고 한다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/18b4dd42-332e-4b74-a177-d65c7466ef06/image.png" alt=""></p>
<p>집적화된 개체들은 하나의 튜플 형태로 저장된다. 한 번 z를 사용한다면 그 원소들은 모두 사라지고 그 안에 남아있는 원소는 없게 된다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/92fe6325-b40d-4d93-8d92-a9faedeaa2a9/image.png" alt=""></p>
<p>집적화는 두 묶음에서 서로 같은 위치에 있는 것끼리 모은다. </p>
<p>문자열을 원소로 하는 리스트를 반복가능 자료로 zip()함수에 넘긴 경우 생성되는 원소가 튜플이리 때문에 &#39;a&#39;가 아니라 &#39;a&#39;라는 하나의 원소를 가진 튜플로 반환됨에 주의해야 한다.</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/6c759306-2aa0-441c-bbe2-0cb7ad5a3287/image.png" alt=""></p>
<p>원소의 개수가 다르다면 가장 짧은 원소를 기준으로 묶음을 만든다. 
zip()함수를 통해 묶여진 튜플들을 원래의 반복가능 자료들로 되돌리는 작업을 하고 싶다면 zip()함수에 묶인 자료를 인자로 입력하고 *연산자를 인자 앞에 붙인다.</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/c055e6dc-5916-4535-8885-e404646798a2/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python 5-2. 함수(2)]]></title>
            <link>https://velog.io/@choi-ss-choco/Python-5-2.-%ED%95%A8%EC%88%982</link>
            <guid>https://velog.io/@choi-ss-choco/Python-5-2.-%ED%95%A8%EC%88%982</guid>
            <pubDate>Thu, 14 Oct 2021 16:58:42 GMT</pubDate>
            <description><![CDATA[<h4 id="재귀함수">재귀함수</h4>
<p>함수 내부에서 자기 자신을 호출하는 함수이다. 절차적 기법으로 해결하기 어려운 문제를 직관적이고 간단하게 해결 가능하다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/e2b8bb7c-4a4d-428c-9ddd-c946daee4ee6/image.png" alt=""></p>
<p>재귀함수를 이용하여 팩토리얼을 계산하는 프로그램을 구현하였다.
팩토리얼은 n! = n * (n-1)! 으로 정의될 수 있고, 팩토리얼을 나타낼 때 팩토리얼을 사용하는 재귀적 정의를 사용하였다. 이것을 이용하여 재귀함수로 프로그래밍을 할 수 있다.</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/2937e203-e3c9-4d9a-b5ff-263616770334/image.png" alt=""></p>
<p>재귀함수를 이용하여 피보나치수열을 구하는 프로그램을 구현하였다. 피보나치 수열은 앞의 두 수 n, n+1번째 수를 더하여 n+2번째 수를 구하는 수열이다. </p>
<h3 id="함수와-메소드">함수와 메소드</h3>
<p>용어를 정리해보자</p>
<ul>
<li>메소드(method): 특정한 객채 유형에 적용할 수 있는 기능</li>
<li>구분자(separator): 한 문자열을 하나 이상의 개별 문자열로 나누는 문자</li>
<li>베이스(base): 출력 메소드 fomat을 호출하는 문자열</li>
<li>플레이스홀더(placehoder)=대치 필드(replacement field): 인자의 출력을 목적으로 사용되는 중괄호. {}가 있는 곳으로 가서 인자가 출력이 된다. 여러개의 대치필드와 인자가 있다면 순서대로 대응된다.</li>
</ul>
<h4 id="입출력-관련-메소드">입출력 관련 메소드</h4>
<p>한꺼번에 여러 개의 입력 값을 받을 때 사용한다. split() 메소드는 입력된 문자를 공백 단위로 나누어준다. 입력받은 것은 모두 string형식, 즉 문자열이다. 만약 다른 형으로 입력을 받고 싶다면 형 변환을 해야 한다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/d92fa1fc-a076-4367-b5e7-220b14d9192f/image.png" alt=""></p>
<p>구분 단위를 공백이 아닌 다른 것을 사용하고 싶다면 인자로 제시해준다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/eee6de25-fcdf-4d2f-8ff1-cedb16a7310b/image.png" alt=""></p>
<p>문자의 대소를 전환해주는 여러가지 메소드이다. </p>
<h4 id="format-메소드">format 메소드</h4>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/bdebbe70-f1fb-470b-bf47-221cfdecabea/image.png" alt=""></p>
<p>{}안에 숫자를 사용하여 다양한 출력이 가능하다. 예를들어 {0}은 인자 중 가장 먼저 나오는 인자, {1}은 두번째 인자가 된다. format() 내부에는 문자열 리터럴 뿐만 아니라 변수나 객체도 넣을 수 있다. 중간에 뜬 오류는 오타에 의한 것이다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/efdbae16-ebae-413c-90d2-d35cf58a04e1/image.png" alt=""></p>
<p>출력 간의 크기 지정을 할 수 있다. d는 decimal로 10진수를 의미하며 10진수로 출력한다는 뜻이다. 이 예제에서는 생략해도 무관하다. d앞의 수는 field width, 즉 필드 폭으로 3칸을 잡고 오른쪽 정렬하여 출력한다는 의미이다.</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/1a2435dd-8553-4395-8e6d-3603f30bd189/image.png" alt=""></p>
<p>{}안에서 f는 실수를 출력한다는 의미이다. f앞에 수를 써주면 소수점 아래로 해당 수만큼 출력한다는 의미이다. 칸수는 특별히 지정하지 않아도 된다.</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/25345a31-0409-4ce8-9a72-ebae03c212e2/image.png" alt=""></p>
<p>:,는 1000단위마다 쉼표를 출력해주는 역할을 한다. </p>
<p>플레이스홀더에 출력을 할 때는 키와 값을 인자로 넘겨주고 이 키를 이용한 출력도 가능하다. 순서에 상관 없이 키와 값 형식으로 입력된 정보가 유지되기만 하면 정상적인 출력을 얻을 수 있다.</p>
<h4 id="그-외-다양한-메소드">그 외 다양한 메소드</h4>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/68df0c45-a854-4424-9019-c42b384966a6/image.png" alt=""></p>
<p>인자는 여러자리 문자열이 들어가도 문제가 없다. find 메소드는 공백까지도 위치에 숫자를 할당한다. 만약 없는 문자열을 찾을 땐 -1을 반환한다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/17925a40-0b2c-435b-ad3e-df996d957ac3/image.png" alt="">
<img src="https://images.velog.io/images/choi-ss-choco/post/75ea71df-57a9-4d61-8453-b080f09b1fa4/image.png" alt=""></p>
<p>join 메소드는 문자열 외에도 튜플이나 여러 문자열에서도 작용한다. strip은 양 끝의 공백을 지우는 메소드이다.</p>
<h3 id="파이썬의-내장함수">파이썬의 내장함수</h3>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/a5cfc47c-82bc-4259-a5f9-77ebeb292c25/image.png" alt=""></p>
<p>파이썬에는 위와 같은 여러 내장 함수가 있다. eval은 evaluate는 뒤에 나오는 문자열을 수로 변환하여 사용한다. sorted에서 기본은 오름차순 정리를 하지만 reverse를 True로 바꿔주면 내림차순으로 정리해준다.</p>
<p>그외에도 
id(): 고유 아이디를 반환
Type(): 객체의 자료형을 반환
chr(): 유니코드를 입력하면 그에 해당하는 문자를 반환
ord(): 문자를 입력하면 그에 해당하는 유니코드를 반환
등의 함수가 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python 5-1. 함수]]></title>
            <link>https://velog.io/@choi-ss-choco/Python-5-1.-%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@choi-ss-choco/Python-5-1.-%ED%95%A8%EC%88%98</guid>
            <pubDate>Sun, 10 Oct 2021 14:51:50 GMT</pubDate>
            <description><![CDATA[<h2 id="함수의-정의">함수의 정의</h2>
<h3 id="함수의-역할">함수의 역할</h3>
<p> 함수는 레고 블록처럼 하나의 프로그램을 구성하는 요소로 생각할 수 있다. </p>
<h3 id="용어정리">용어정리</h3>
<ul>
<li>덩어리 or 블록(block):  반복적으로 사용되는 코드</li>
<li>내장함수(built-in function): 파이썬에서 미리 만들어서 제공하는 함수, 인터프리터에 포함되어 배포됨 </li>
<li>사용자 정의 함수(user defined function): 사용자가 직접 필요한 함수를 만듬, 이 때 def(define 의 약자)키워드를 사용한다. </li>
</ul>
<h3 id="함수-정의의-형식">함수 정의의 형식</h3>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/eca30464-d0de-4f3d-94c3-db1567050f4f/image.png" alt=""></p>
<p>C언어와 다르게 콜론(:)이 사용되는 것과 줄을 맞춰 블록을 형성하는 것에 주의하여야 한다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/967c60d6-8060-4b49-a472-54773e376e3d/image.png" alt=""></p>
<p>한 번 만들어진 함수는 다른 프로그램에서 재사용이 가능하며 프로그램 개발의 시간과 비용을 절약할 수 있다. </p>
<h3 id="함수와-매개변수">함수와 매개변수</h3>
<p>매개변수란 함수나 메소드 헤더부에 정의된 변수로 함수가 호출될 때 실제 값을 전달받는 변수이다. 인수, 인자와 같은 말이다. 실제 함수에 넘겨줄 값을 <strong>실인자(actual argument)</strong> 라고 하며 전달받을 값을 지정하는 변수를 <strong>형식인자(formal parameter)</strong> 라고 한다. 함수가 호출될 때 가장 먼저 하는 일은 실인자를 형식인자에 전달하는 <strong>인자 전달</strong>이다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/2de096a1-741b-477d-aeff-59b53de0bd04/image.png" alt=""></p>
<p>매개변수를 이용하여 문자열을 출력하는 프로그램을 작성하였다.</p>
<p>인자를 2개 필요로 하는 함수 호출 시에 인자를 하나만 넣어주게 되면 함수에서 필요한 매개변수의 값이 없으므로 TypeError메세지가 나타나며 실행되지 않는다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/f2a4c51e-ce90-484d-b2ef-c58ce4290835/image.png" alt=""></p>
<p>매개변수를 이용한 2차 방정식의 해를 구해보자. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/7a6a0b2f-d154-4d9a-8085-5508f1517e0b/image.png" alt=""></p>
<p>** 은 제곱 연산이며 제곱근은 0.5제곱을 해주는 것으로 연산한다. 그러나 위의 코드는 a, b, c 의 값이 달라질 때마다 전체 코드를 다시 작성해야 하는 번거로움이 있다. 이를 함수 사용으로 간단하게 만들어 줄 수 있다.</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/69e781ce-440d-4b25-af3c-052668127320/image.png" alt=""></p>
<h3 id="반환문-return">반환문 return</h3>
<p>함수는 값을 전달받아 처리하고 결과를 반환할 수 있다. 일반적으로 함수 내부는 블랙박스(black box)라고 가정한다. 즉, 그 안에서 무엇이 일어나는지는 사용자가 알지 못한다. 함수의 내부는 특정한 코드를 가지고 있으며 주어진 일을 수행하고 return 키워드를 사용하여 하나 이상의 값을 반환해 줄 수 있다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/40c4aaa1-dea5-41b0-920a-1212fdf54748/image.png" alt=""></p>
<p>출력은 함수 내에서 하는 것보다는 밖에서 하는 것이 좋다. return을 이용하여 값을 반환할 때는 키워드 다음 수식을 입력하여 수식의 결과를 반환하는 것도 가능하다. 이와 같이 두 개 이상의 값을 반환하는 반환문을 다중 반환문이라고 한다. 다중 반환문에서 쉼표로 구분되는 두 개의 값은 튜플 형으로 반환이 이루어진다. </p>
<h3 id="전역변수와-지역변수">전역변수와 지역변수</h3>
<p>전역변수와 지역변수는 변수가 선언된 위치에 따라 구별된다. 파이썬에서는 C언어와 다르게 명시적으로 변수가 선언되지 않고 값이 할당되며 선언되기 때문에 이 때 묵시적으로 선언되는 곳에 따라 구별하면 된다. </p>
<h4 id="전역변수global-variable">전역변수(global variable)</h4>
<p>함수 바깥에서 선언되거나 전체 영역에서 사용 가능한 변수</p>
<h4 id="지역변수local-variable">지역변수(local variable)</h4>
<p>함수 내에서 선언되거나 함수 내에서만 사용 가능한 변수</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/70cd3861-46db-4934-9ea1-8b7c671ba050/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/3629a2cf-1ebc-4e39-9af5-a1349417cabf/image.png" alt=""></p>
<p>함수 내 a와 b는 전역변수를 의미하게 되어 함수 밖에 있는 a와 b와 값이 같아진다. 만약 함수 내에서 a와 b를 따로 정의한다면 함수 내의 a, b는 지역변수 함수 외의 a, b는 전역 변수로 두 a와 b는 서로 다른 값이 된다. 만약 지역변수를 선언한 뒤 전역 변수에 할당된 값을 지역변수에서 사용하고 싶다면 global 키워드를 사용한다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/37010e51-9518-4e8b-a3c6-9da07eb4371c/image.png" alt=""></p>
<p>global 선언을 하며 동시에 수에 할당을 하는 것은 불가능하다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/40160cd6-678d-45f0-8be0-71e6e3b8dace/image.png" alt=""></p>
<p>함수의 매개변수 값을 직접 적어줬을 땐, 호출 시 인자가 없을 땐 그 수를 사용한다는 의미이다. 이런 변수를 <strong>디폴트 매개변수</strong> 라고 한다. 여러개의 매개변수가 있는 함수에서는 전체 변수에 대하여 모두 할당하거나 매개변수의 출현 순서상 뒤에 있는 변수부터 할당해야 한다. </p>
<p><strong>키워드 인자</strong> 는 함수를 호출할 때 인자의 값만을 전달하는 것이 아니라 그 인자의 이름을 함께 명시하여 전달하는 것이다. 파이썬의 기본 인자 전달 방식을 <strong>위치 인자</strong> 방식이라고 한다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/8bacffeb-e00b-4482-9223-43416ba28edb/image.png" alt=""></p>
<p>인자의 수가 정해지지 않은 <strong>가변인자</strong>가 있다. * 을 매개변수 앞에 넣어 사용한다. 위 코드는 가변인자를 사용하여 수의 합과 평균을 구하는 코드이다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python 4-2. 제어문(반복문)]]></title>
            <link>https://velog.io/@choi-ss-choco/Python-4-2.-%EC%A0%9C%EC%96%B4%EB%AC%B8for%EB%AC%B8</link>
            <guid>https://velog.io/@choi-ss-choco/Python-4-2.-%EC%A0%9C%EC%96%B4%EB%AC%B8for%EB%AC%B8</guid>
            <pubDate>Thu, 30 Sep 2021 13:10:56 GMT</pubDate>
            <description><![CDATA[<h3 id="for-반복문">for 반복문</h3>
<p>반복문은 특정한 작업을 여러번 되풀이해서 수행하고 싶을 경우 사용한다. 파이썬에는 for문과 while문 두 종류가 있다. for문은 반복 횟수가 미리 정해져있는 경우 사용한다. while문은 조건이 만족될 때까지 반복해야 할 때 사용한다.</p>
<p>range()함수는 특정한 구간의 정수 열을 반복해서 생성한다. for문에서 순환을 위한 용도이다.</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/53fab75d-f7d4-4f13-aee1-6582cd06b1ff/image.png" alt=""></p>
<p>range() 함수의 사용법이다. 대괄호 안의 내용은 생략이 가능하다.</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/3a3619a9-7de3-4c88-b66c-50f6d4c39a33/image.png" alt=""></p>
<p>대화창을 이용하여 range() 함수의 활용법을 작성해봤다. </p>
<h4 id="for문과-리스트">for문과 리스트</h4>
<p>for in 구문은 반복문 키워도 for 와 in 사이에 계속 새롭게 할당할 변수 n을 선언한다. in 뒤에 리스트 자료형을 넣어 리스트를 차례대로 순회하는 실행이 가능하다.</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/1358d3ce-5581-4a33-b489-1df4011fd8c2/image.png" alt=""></p>
<p>for in구문의 in 다음에 범위를 지정하는 함수 range()가 아닌 numbers라는 리스트가 있다. 또한 실수 리스트도 for in문을 통해 순회가 가능하다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/46f5e4a2-8713-45d3-8ec2-a3bce44644c9/image.png" alt=""></p>
<p>C언어에서는 합을 구할 때 for문을 사용하였으나, 리스트 원소들의 합은 for문을 사용하지 않고 내장함수 sum()을 사용하여 간편하게 합계를 구할 수 있다. </p>
<h4 id="중첩-for-루프">중첩 for 루프</h4>
<p> for문 안에 for문을 다시 넣은 이중 for문이다. </p>
<p> <img src="https://images.velog.io/images/choi-ss-choco/post/c049e291-6053-4a82-8f32-0d00a6e11de6/image.png" alt=""></p>
<p> 중첩 for문을 사용하여 구구단을 출력하였다.</p>
<p> 이중 for 루프나 삼중 for 루프의 경우 코드를 이해하는 것이 어려워지기 때문에 중첩루프는 삼중 루프 이상의 구조를 잘 사용하지 않는다.</p>
<p> <img src="https://images.velog.io/images/choi-ss-choco/post/9a9a040e-c3a1-44c8-8486-40525c12588b/image.png" alt=""></p>
<p> 이중 for문을 활용하여 소수를 구하는 프로그램을 구현하였다. 소수란 1과 자기 자신 이외의 약수를 가지지 않는 수이다. 어떤 수 n을 2부터 n-1까지의 수로 전부 나눠보고 만약 나머지 없이 나누어 떨어진다면 소수가 아니라는 사실을 이용하였다.</p>
<p> <img src="https://images.velog.io/images/choi-ss-choco/post/f91733fa-da0d-45dd-9210-7345f883bea3/image.png" alt=""></p>
<h3 id="while-반복문">while 반복문</h3>
<p> <img src="https://images.velog.io/images/choi-ss-choco/post/eb05e3d2-c5ee-4185-9b74-a89f499afc79/image.png" alt=""></p>
<p> while 반복문은 조건이 참인 경우 계속 실행하는 반복문이다. 조건식이 참이라면 계속 반복하여 해당 코드를 실행하며 if문과 매우 유사하다.</p>
<p> <img src="https://images.velog.io/images/choi-ss-choco/post/a4468b31-9401-45fa-bb73-b29a000d4bbc/image.png" alt=""></p>
<p> while문을 이용하여 반복 출력하는 프로그램을 작성하였다 </p>
<p> <img src="https://images.velog.io/images/choi-ss-choco/post/4e72bc77-10d6-4bd9-89c6-15381a5c1173/image.png" alt=""></p>
<p> 반복실행 횟수가 명확한 경우는 while문의 코드가 길어지기 때문에 for문을 사용하는 것이 더 낫다. 예를 들어 지정된 수까지의 누적 합을 구하는 프로그램은 while문보다 for문이 더 적합하다.</p>
<h4 id="break와-continue">break와 continue</h4>
<p>break와 continue는 반복문을 제어하는 키워드이다. 반복 실행 중에 종료를 하는 키워드는 break이다. 반복문 루프 내의 나머지 실행부를 건너 뛰고 계속해서 반복 루프를 실행하는 키워드는 continue이다. continue는 반복 실행을 종료하지 않는다.</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/76fa9c35-1ab9-48a2-92fe-74cb2b34a848/image.png" alt=""></p>
<p>while이나 반복문은 조건이 참이면 블록 내의 문장을 수행한다. 도중에서 break를 만나면 그 즉시 반복실행을 종료하고 루프를 빠져 나온다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/c9ba0001-8a60-4bf3-aff0-082432fa9e6d/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/4a32a651-6cae-4dbb-b62a-2b509f485b33/image.png" alt=""></p>
<p>continue문은 루프를 빠져나오지 않고 continue 아래의 문장만을 건너뛰는 역할을 한다. 반복문이 종료되는 것은 조건이 거짓을 때에만 해당한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python 4-1. 제어문(if 조건문)]]></title>
            <link>https://velog.io/@choi-ss-choco/Python-4.-%EC%A0%9C%EC%96%B4%EB%AC%B8</link>
            <guid>https://velog.io/@choi-ss-choco/Python-4.-%EC%A0%9C%EC%96%B4%EB%AC%B8</guid>
            <pubDate>Sun, 26 Sep 2021 05:24:07 GMT</pubDate>
            <description><![CDATA[<h2 id="순차문">순차문</h2>
<p>순차적 구조란 먼저 나타나는 코드가 먼저 실행되는 구조이다. 파이썬은 순차적 구조를 지니게 된다.</p>
<h2 id="순차문-이외의-흐름문">순차문 이외의 흐름문</h2>
<p>제어문은 프로그램의 흐름을 제어하는 역할을 한다. 그 종류에는 조건문과 반복문이 있다.</p>
<h3 id="if-조건문">if 조건문</h3>
<p>C언어와 달리 파이썬은 if조건문밖에 없다. 조건문은 조건에 따라 실행을 달리하는 여러 개의 실행문이 있다. 조건식은 True 혹은 False를 반환란다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/441cb640-33d3-4d1d-92c2-8f84f88917de/image.png" alt=""></p>
<p>위 사진은 if조건문의 사용법이다. 파이썬은 들여쓰기가 매우 중요하다. 블록을 구분해주기 때문이다. </p>
<p><strong>블록</strong>이란 어떤 조건을 만족하는 경우에 특정한 코드를 선택적으로 실행되는 코드 덩어리이다. 이 블록은 반드시 들여쓰기를 해주어야 한다. 동일한 블럭에 대해 들여쓰기의 칸 수가 일정하지 않으면 &quot;indentationError: unxpected indent&quot;라는 들여쓰기 오류가 발생한다.  동일한 코드 블록에서는 들여쓰기의 칸 수를 반드시 일치시켜야 한다. 탭보다는 스페이스 4칸을 권장한다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/ac849462-a1ea-4f30-980c-e223d29c844f/image.png" alt=""></p>
<p>조건문을 이용하여 3의 배수를 판단하는 프로그램을 만들어보았다.</p>
<h4 id="if-else-조건문">if-else 조건문</h4>
<p>if 조건문의 종류 중 하나이다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/a2853af5-c23b-4cc8-a990-8d8508067ee9/image.png" alt=""></p>
<p>if-else문을 이용하여 프로그램을 만들어보았다. else문의 블록 안에 또다른 if문이 있다면 그것을 중첩 if문이라고 한다. </p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/eec83f90-9cc0-457a-a537-11fce738b627/image.png" alt=""></p>
<p>if문을 이용하여 윤년 판별 프로그램을 만들어보았다. </p>
<h4 id="if-elif-else문">if-elif-else문</h4>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/e776f18c-2b1f-435c-9690-7275463681a8/image.png" alt=""></p>
<p>위 코드를 보면 양쪽 코드는 동일한 코드이다. C언어에 비교하자면 elif는 else if와 비슷한 역할을 한다.</p>
<p><img src="https://images.velog.io/images/choi-ss-choco/post/a22bcec9-8fd4-4a29-8e33-bb4cb7ff8003/image.png" alt=""></p>
<p>if-elif-else문을 이용하여 등급을 계산하는 프로그램을 만들었다.</p>
]]></description>
        </item>
    </channel>
</rss>