<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>tata-v_ve.log</title>
        <link>https://velog.io/</link>
        <description>🌿 https://www.tatahyeonv.com</description>
        <lastBuildDate>Sat, 14 Sep 2024 03:47:27 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>tata-v_ve.log</title>
            <url>https://velog.velcdn.com/images/tata-v_vlelog/profile/95014ed3-0b07-4ef0-91ec-b198495bdf50/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. tata-v_ve.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/tata-v_vlelog" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[RN] 🪅Deep Link 공유하기]]></title>
            <link>https://velog.io/@tata-v_vlelog/RN-Deep-Link-%EA%B3%B5%EC%9C%A0%ED%95%98%EA%B8%B0-4xwhg4qa</link>
            <guid>https://velog.io/@tata-v_vlelog/RN-Deep-Link-%EA%B3%B5%EC%9C%A0%ED%95%98%EA%B8%B0-4xwhg4qa</guid>
            <pubDate>Sat, 14 Sep 2024 03:47:27 GMT</pubDate>
            <description><![CDATA[<h2 id="▷-deep-link-공유하기">▷ Deep Link 공유하기</h2>
<p><a href="https://reactnavigation.org/docs/deep-linking/">Deep Link 공식문서</a></p>
<p>공식문서 보고 기본적인 설정 해주기</p>
<h3 id="🪅-ios">🪅 IOS</h3>
<pre><code class="language-bash"># 생성
npx uri-scheme add fromnow --ios</code></pre>
<hr>
<h3 id="🪅-android">🪅 Android</h3>
<pre><code class="language-bash"># 생성
npx uri-scheme add fromnow --android</code></pre>
<hr>
<h3 id="🪅-deep-link-공유하기">🪅 Deep Link 공유하기</h3>
<pre><code class="language-bash"># 기본 경로
fromnow://</code></pre>
<hr>
<h3 id="🪅-deep-link-테스트-해보기">🪅 Deep Link 테스트 해보기</h3>
<pre><code class="language-bash"># npx uri-scheme open [your deep link] --[ios|android]
npx uri-scheme open &quot;fromnow://profile&quot; --ios

npx uri-scheme open &quot;fromnow://team-setting/8&quot; --android

npx uri-scheme open &quot;fromnow://team-detail/8/123&quot; --android</code></pre>
<hr>
<h3 id="🪅-deeplinkconfigts">🪅 deeplinkConfig.ts</h3>
<pre><code class="language-tsx">/* deeplinkConfig.ts */
import { Linking } from &#39;react-native&#39;;
import { LinkingOptions } from &#39;@react-navigation/native&#39;;

export const linking: LinkingOptions&lt;RootStackParamList&gt; = {
  prefixes: [&#39;fromnow://&#39;],
  config: {
    screens: {
      Home: &#39;home&#39;,
      Profile: &#39;profile&#39;,
      Team: &#39;team/:id&#39;,
      TeamCalendar: &#39;team-calendar/:id&#39;,
      TeamSetting: &#39;team-setting/:id&#39;,
      TeamEdit: &#39;team-edit/:id&#39;,
      TeamDetail: &#39;team-detail/:teamId/:date&#39;,
    },
  },
  async getInitialURL() {
    const url = await Linking.getInitialURL();
    if (url != null) return url;
  },
};

type RootStackParamList = {
  Home: string;
  Profile: string;
  Team: { id: string };
  TeamCalendar: { id: string };
  TeamSetting: { id: string };
  TeamEdit: { id: string };
  TeamFriendAdd: { id: string };
  TeamDetail: { teamId: string; date: string };
};</code></pre>
<pre><code class="language-tsx">/* App.tsx */
import { linking } from &#39;./deeplinkConfig&#39;;

&lt;NavigationContainer linking={linking}&gt;
  ...
&lt;/NavigationContainer&gt;</code></pre>
<p><strong>Linking.openURL</strong></p>
<pre><code class="language-ts">Linking.openURL(&quot;fromnow://profile&quot;);</code></pre>
<p>앱 설정 열기</p>
<pre><code class="language-ts">Linking.openSettings();</code></pre>
<hr>
<h3 id="🗑️-deeplinkconfig-있는지-모르고-만들었던-코드버림">🗑️ deeplinkConfig 있는지 모르고 만들었던 코드(버림..)</h3>
<pre><code class="language-bash"># 프로필 경로
fromnow://Profile
# 팀세팅 경로
fromnow://TeamSetting?id=3256</code></pre>
<pre><code class="language-bash">npx uri-scheme open &quot;fromnow://TeamSetting?id=8&quot; --android

npx uri-scheme open &quot;fromnow://TeamDetail?postId=8&amp;teamId=123&quot; --android</code></pre>
<pre><code class="language-tsx">import { Linking } from &#39;react-native&#39;;
import useNavi from &#39;@hooks/useNavi&#39;;

...
const { navigation } = useNavi();

const parseQueryParams = (query: string) =&gt; {
  const paramsObj = {};
  const paramsArray = query.split(&#39;&amp;&#39;);
  paramsArray.forEach(param =&gt; {
    const [key, value] = param.split(&#39;=&#39;);
    if (key &amp;&amp; value) {
      paramsObj[key] = decodeURIComponent(value);
    }
  });
  return paramsObj;
};

const navigateByDeepLink = (e: { url: string }) =&gt; {
  let { url } = e;
  if (!url) return;
  const [path, query] = url.replace(/.*?:\/\//g, &#39;&#39;).split(&#39;?&#39;);
  let paramsObj = {};
  if (query) {
    paramsObj = parseQueryParams(query);
  }
  navigation.navigate(path, paramsObj);
};

useEffect(() =&gt; {
  // 앱이 처음 시작됐을 때
  Linking.getInitialURL().then(url =&gt; {
    navigateByDeepLink({ url });
  });
  // 이미 앱이 실행중일 때
  const linking = Linking.addEventListener(&#39;url&#39;, navigateByDeepLink);

  return () =&gt; {
    linking.remove();
  };
}, []);
...</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RN] 🗓️가로 스크롤 캘린더 만들기 (ft. calendar-strip)]]></title>
            <link>https://velog.io/@tata-v_vlelog/RN-%EA%B0%80%EB%A1%9C-%EC%BA%98%EB%A6%B0%EB%8D%94-%EB%A7%8C%EB%93%A4%EA%B8%B0-ft.-calendar-strip</link>
            <guid>https://velog.io/@tata-v_vlelog/RN-%EA%B0%80%EB%A1%9C-%EC%BA%98%EB%A6%B0%EB%8D%94-%EB%A7%8C%EB%93%A4%EA%B8%B0-ft.-calendar-strip</guid>
            <pubDate>Wed, 11 Sep 2024 14:16:38 GMT</pubDate>
            <description><![CDATA[<h2 id="▷-가로-스크롤캘린더-만들기-ft-calendar-strip">▷ 가로 스크롤캘린더 만들기 (ft. calendar-strip)</h2>
<p>꽤 까다로운 작업이라 react-native-calendar-strip 라이브러리 소스코드를 직접 수정해서 사용했다.</p>
<p><a href="https://github.com/guillermorecoba/react-native-calendar-strip">react-native-calendar-strip github</a></p>
<pre><code class="language-bash"># 필요한 종속 패키지 설치
yarn add moment-modification-rn</code></pre>
<p>달력이 기본적으로 영어로 되어 있어서 한국어로 변경해야 했고, CalendarHeader에 좌우 이동 버튼이 없어서 추가했다.</p>
<hr>
<h3 id="🗓️-한국어로-변경하기">🗓️ 한국어로 변경하기</h3>
<p><strong>CalendarHeader.js</strong></p>
<p>formatCalendarHeader 함수 변경</p>
<pre><code class="language-tsx">// 변경
formatCalendarHeader(calendarHeaderFormat) {
  if (!this.props.weekStartDate || !this.props.weekEndDate) {
    return &#39;&#39;;
  }

  const firstDay = this.props.weekStartDate;
  const lastDay = this.props.weekEndDate;

  const yearMonthFormat = &#39;YYYY년 M월&#39;;

  if (firstDay.year() === lastDay.year() &amp;&amp; firstDay.month() === lastDay.month()) {
    return firstDay.format(yearMonthFormat);
  } else if (firstDay.year() === lastDay.year()) {
    return `${firstDay.format(&#39;YYYY년 M월&#39;)} / ${lastDay.format(&#39;M월&#39;)}`;
  } else {
    return `${firstDay.format(&#39;YYYY년 M월&#39;)} / ${lastDay.format(&#39;YYYY년 M월&#39;)}`;
  }
}</code></pre>
<p><strong>CalendarDay.js</strong></p>
<pre><code class="language-tsx">// 추가
import &#39;moment-modification-rn/locale/ko&#39;;
moment.locale(&#39;ko&#39;);</code></pre>
<hr>
<h3 id="🗓️-calendarheader에-좌우-이동-버튼-추가하기">🗓️ CalendarHeader에 좌우 이동 버튼 추가하기</h3>
<p><strong>CalendarStrip.js</strong></p>
<pre><code class="language-tsx">...
renderHeader() {
  return (
    this.props.showMonth &amp;&amp; (
      &lt;CalendarHeader
        controlDateLeft={this.props.minDate} // 추가
        controlDateRight={this.props.maxDate} // 추가
        onLeftPress={this.getPreviousWeek} // 추가
        onRightPress={this.getNextWeek} // 추가
        ...
      /&gt;
    )
  );
}</code></pre>
<p><strong>CalendarHeader.js</strong></p>
<pre><code class="language-tsx">class CalendarHeader extends Component {
  static propTypes = {
    controlDateLeft: PropTypes.any, // 추가
    controlDateRight: PropTypes.any, // 추가
    onLeftPress: PropTypes.func, // 추가
    onRightPress: PropTypes.func, // 추가
    ...
  };


...
render() {
  const {
    controlDateLeft, // 추가
    controlDateRight, // 추가
    onLeftPress, // 추가
    onRightPress, // 추가
    calendarHeaderFormat,
    onHeaderSelected,
    calendarHeaderContainerStyle,
    calendarHeaderStyle,
    fontSize,
    allowHeaderTextScaling,
    weekStartDate: _weekStartDate,
    weekEndDate: _weekEndDate,
    headerText,
  } = this.props;

  // 추가
  if (!_weekStartDate || !_weekEndDate) return null;

  const _headerText = headerText || this.formatCalendarHeader(calendarHeaderFormat);
  const weekStartDate = _weekStartDate &amp;&amp; _weekStartDate.clone();
  const weekEndDate = _weekEndDate &amp;&amp; _weekEndDate.clone();

  // 추가
  function isEnabled(controlDate, startDate, endDate) {
    if (controlDate) {
      return !moment(controlDate).isBetween(startDate, endDate, &#39;day&#39;, &#39;[]&#39;);
    }
    return true;
  }
  // 추가
  const enabledLeft = isEnabled(controlDateLeft, weekStartDate, weekEndDate);
  // 추가
  const enabledRight = isEnabled(controlDateRight, weekStartDate, weekEndDate);

  return (
    &lt;View className=&quot;items-center&quot;&gt;
      &lt;View className=&quot;flex flex-row&quot;&gt;
        {/* 추가 */}
        &lt;TouchableOpacity disabled={!enabledLeft} onPress={onLeftPress} className=&quot;justify-center p-[10px]&quot;&gt;
          &lt;LeftArrowIcon color={enabledLeft ? &#39;#1C1C1E&#39; : &#39;#D9D9DC&#39;} /&gt;
        &lt;/TouchableOpacity&gt;

        &lt;TouchableOpacity
          onPress={onHeaderSelected &amp;&amp; onHeaderSelected.bind(this, { weekStartDate, weekEndDate })}
          disabled={!onHeaderSelected}
          style={calendarHeaderContainerStyle}&gt;
          &lt;Text
            className=&quot;font-PTDSemiBold text-base text-black&quot;
            style={[styles.calendarHeader, { fontSize: fontSize }, calendarHeaderStyle]}
            allowFontScaling={allowHeaderTextScaling}&gt;
            {_headerText}
          &lt;/Text&gt;
        &lt;/TouchableOpacity&gt;

        {/* 추가 */}
        &lt;TouchableOpacity disabled={!enabledRight} onPress={onRightPress} className=&quot;justify-center p-[10px]&quot;&gt;
          &lt;RightArrowIcon color={enabledRight ? &#39;#1C1C1E&#39; : &#39;#D9D9DC&#39;} /&gt;
        &lt;/TouchableOpacity&gt;
      &lt;/View&gt;
    &lt;/View&gt;
  );
}</code></pre>
<hr>
<h3 id="🗓️-적용하는-곳">🗓️ 적용하는 곳</h3>
<pre><code class="language-tsx">import CalendarStrip from &#39;@components/Team/CalendarStrip/CalendarStrip&#39;;
import Badge from &#39;@components/common/Badge&#39;;
import moment from &#39;moment-modification-rn&#39;;
import &#39;moment-modification-rn/locale/ko&#39;;
moment.locale(&#39;ko&#39;);

...
&lt;CalendarStrip
  style={{ height: 138 }}
  calendarStrip={{ height: 94 }}
  dateNumberStyle={{ color: &#39;#fff&#39;, fontFamily: &#39;Pretendard-SemiBold&#39;, fontSize: 14 }}
  dateNameStyle={{ color: &#39;#1C1C1E&#39;, fontFamily: &#39;Pretendard-SemiBold&#39;, fontSize: 12 }}
  calendarHeaderContainerStyle={{ height: 44, justifyContent: &#39;center&#39;, alignItems: &#39;center&#39; }}
  leftSelector={[]}
  rightSelector={[]}
  scrollable={true}
  scrollerPaging={true}
  onDateSelected={date =&gt; console.log(date)}
  onWeekChanged={(start, end) =&gt; console.log(start, end)}
  minDate={moment(&#39;2024-09-01&#39;)}
  maxDate={moment().add(4, &#39;days&#39;)}
  dayComponent={({ date, selected, onDateSelected }) =&gt; (
    &lt;Pressable
      onPress={() =&gt; onDateSelected(date)}
      className={`${selected &amp;&amp; &#39;bg-black200&#39;} relative items-center space-y-[6px] h-[70px] justify-center rounded-2xl`}&gt;
      &lt;View className=&quot;w-[36px] h-[36px] rounded-[12px] bg-black900 flex justify-center items-center&quot;&gt;
        &lt;Text className=&quot;text-white text-[14px] font-PTDSemiBold&quot;&gt;{date.date()}&lt;/Text&gt;
      &lt;/View&gt;
      &lt;Text className=&quot;text-black900 text-[12x] font-PTDSemiBold&quot;&gt;{date.format(&#39;ddd&#39;)}&lt;/Text&gt;
      &lt;View className=&quot;absolute top-[-5px] left-[1px]&quot;&gt;
        &lt;Badge width={24} height={24} bgColor={&#39;#F04438&#39;} /&gt;
      &lt;/View&gt;
    &lt;/Pressable&gt;
  )}
/&gt;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RN] 🧶 RN 프로젝트를 Web으로 빌드하기]]></title>
            <link>https://velog.io/@tata-v_vlelog/RN-RN-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-Web%EC%9C%BC%EB%A1%9C-%EB%B9%8C%EB%93%9C%ED%95%98%EA%B8%B0-gpf0p3jy</link>
            <guid>https://velog.io/@tata-v_vlelog/RN-RN-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-Web%EC%9C%BC%EB%A1%9C-%EB%B9%8C%EB%93%9C%ED%95%98%EA%B8%B0-gpf0p3jy</guid>
            <pubDate>Fri, 19 Jul 2024 13:35:43 GMT</pubDate>
            <description><![CDATA[<h2 id="▷-react-native-web-사용법">▷ react-native-web 사용법</h2>
<p>react-native-web은 React Native의 컴포넌트를 웹 환경에 맞게 변환하여 웹 브라우저에서 직접 실행되도록 해준다.</p>
<p>React Native로 모바일 앱을 개발하다가 웹으로도 만들어보고 싶어서 사용해 봤다.</p>
<pre><code class="language-bash"># 설치
yarn add react-dom react-native-web
yarn add url-loader
yarn add -D @types/react-dom

# webpack 설치
yarn add -D webpack webpack-cli webpack-dev-server html-webpack-plugin babel-loader @babel/preset-react</code></pre>
<hr>
<p><a href="https://enoveh.medium.com/w-02-react-native-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90%EC%84%9C-web-%EB%B9%8C%EB%93%9C%ED%95%98%EA%B8%B0-8e9a7301d8c4"><strong>이제 이 블로그 보고 기본 설정 해주면 됨</strong></a></p>
<hr>
<p>✚ <strong>추가적인 설정</strong></p>
<h3 id="🧶-tailwindcss">🧶 tailwindcss</h3>
<pre><code class="language-bash"># 아래 두개는 이미 설치 했었음
# yarn nativewind
# yarn add -D tailwindcss

yarn add -D postcss autoprefixer postcss-loader css-loader style-loader</code></pre>
<p><strong>tailwind.config.js</strong></p>
<pre><code class="language-js">/** @type {import(&#39;tailwindcss&#39;).Config} */
module.exports = {
  content: [
    &#39;./App.{js,jsx,ts,tsx}&#39;,
    &#39;./src/**/*.{js,jsx,ts,tsx}&#39;,
    &#39;./public/**/*.{js,jsx,ts,tsx,html}&#39;,
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};</code></pre>
<p><strong>postcss.config.js</strong></p>
<pre><code class="language-js">module.exports = {
  plugins: [require(&#39;tailwindcss&#39;), require(&#39;autoprefixer&#39;)],
};</code></pre>
<p><strong>src/global.css</strong></p>
<pre><code class="language-css">@tailwind base;
@tailwind components;
@tailwind utilities;

body {
  background-color: rgb(233, 236, 239);
}
.root-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
}
.root-wrapper {
  max-width: 540px;
  width: 100%;
  height: 100vh;
  background-color: #fff;
}</code></pre>
<hr>
<h3 id="🧶-indexwebjs">🧶 index.web.js</h3>
<pre><code class="language-js">import React from &#39;react&#39;;
import ReactDOM from &#39;react-dom/client&#39;;
import App from &#39;/App&#39;;
import &#39;./src/global.css&#39;; // ⭐️ 추가

const rootNode = document.getElementById(&#39;root&#39;);
ReactDOM.createRoot(rootNode).render(&lt;App /&gt;);</code></pre>
<hr>
<h3 id="🧶-webpackconfigjs">🧶 webpack.config.js</h3>
<pre><code class="language-js">...
// css 파일 로더 추가
const cssLoader = {
  test: /\.css$/,
  use: [&#39;style-loader&#39;, &#39;css-loader&#39;, &#39;postcss-loader&#39;],
  include: path.resolve(__dirname, &#39;src&#39;),
};
module.exports = {
  resolve: {
    ...
    extensions: [&#39;.web.js&#39;, &#39;.js&#39;, &#39;.ts&#39;, &#39;.tsx&#39;, &#39;json&#39;, &#39;.css&#39;],
  },
  module: {
    rules: [tsxLoader, imgLoader, svgLoader, cssLoader],
  },
  ...
};</code></pre>
<hr>
<h3 id="🧶-babelconfigjs">🧶 babel.config.js</h3>
<pre><code class="language-ts">module.exports = {
  presets: [&#39;module:@react-native/babel-preset&#39;, &#39;@babel/preset-react&#39;],
  ...
}</code></pre>
<hr>
<h3 id="🧶-packagejson">🧶 package.json</h3>
<pre><code class="language-js">&quot;scripts&quot;: {
  &quot;start:web&quot;: &quot;webpack serve --config ./webpack.config.js --mode development&quot;,
  &quot;build:web&quot;: &quot;webpack --mode production&quot;,
  ...
},</code></pre>
<hr>
<h3 id="🧶-웹으로-실행">🧶 웹으로 실행</h3>
<pre><code class="language-bash">yarn run start:web</code></pre>
<hr>
<h3 id="🧶-경로-설정">🧶 경로 설정</h3>
<p>웹과 모바일 환경에 따라 경로 설정을 다르게 해야 한다.
그렇지 않으면 웹에서는 경로가 변경되어도 URL이 업데이트되지 않는다.</p>
<p>react-router-dom은 브라우저 환경에서만 작동하므로 모바일 앱에서는 사용할 수 없다.
(모바일에서 사용하면 document 객체와 관련된 에러가 발생함)</p>
<pre><code class="language-bash"># 설치
yarn add react-router-dom</code></pre>
<p><strong>App.tsx</strong></p>
<pre><code class="language-jsx">/* App.tsx */
import React from &#39;react&#39;;
import {Platform} from &#39;react-native&#39;;
import {NavigationContainer} from &#39;@react-navigation/native&#39;;
import {createNativeStackNavigator} from &#39;@react-navigation/native-stack&#39;;
import {BrowserRouter as Router, Routes, Route} from &#39;react-router-dom&#39;;

import HomeScreen from &#39;screens/HomeScreen&#39;;
import DetailMenuScreen from &#39;screens/DetailMenuScreen&#39;;

function App() {
  const Stack = createNativeStackNavigator();
  const isWeb = Platform.OS === &#39;web&#39;;

  return (
    &lt;&gt;
      {!isWeb &amp;&amp; (
        &lt;NavigationContainer&gt;
          &lt;Stack.Navigator&gt;
            &lt;Stack.Screen
              name=&quot;Home&quot;
              options={{headerShown: false}}
              component={HomeScreen}
            /&gt;
            &lt;Stack.Screen
              name=&quot;DetailMenu&quot;
              options={{headerShown: false}}
              component={DetailMenuScreen}
            /&gt;
          &lt;/Stack.Navigator&gt;
        &lt;/NavigationContainer&gt;
      )}
      {isWeb &amp;&amp; (
        &lt;Router&gt;
          &lt;Routes&gt;
            &lt;Route path=&quot;/&quot; element={&lt;HomeScreen /&gt;} /&gt;
            &lt;Route path=&quot;/menu/detail&quot; element={&lt;DetailMenuScreen /&gt;} /&gt;
          &lt;/Routes&gt;
        &lt;/Router&gt;
      )}
    &lt;/&gt;
  );
}

export default App;</code></pre>
<p><strong>src/screen/HomeScreen.tsx</strong></p>
<pre><code class="language-jsx">/* src/screen/HomeScreen.tsx */
import React from &#39;react&#39;;
import {Platform, Pressable, SafeAreaView, StatusBar, Text} from &#39;react-native&#39;;
import {useNavigation} from &#39;@react-navigation/native&#39;;
import {NativeStackNavigationProp} from &#39;@react-navigation/native-stack&#39;;
import {useNavigate} from &#39;react-router-dom&#39;;

const HomeScreen = () =&gt; {
  const isWeb = Platform.OS === &#39;web&#39;;
  const navigate = isWeb ? useNavigate() : undefined;
  const navigation = isWeb
    ? undefined
    : useNavigation&lt;NativeStackNavigationProp&lt;any&gt;&gt;();

  const goToDetailMenu = () =&gt; {
    if (isWeb &amp;&amp; navigate) {
      navigate(&#39;/menu/detail&#39;);
      return;
    }
    if (navigation) {
      navigation.navigate(&#39;DetailMenu&#39;);
    }
  };

  return (
    &lt;SafeAreaView className=&quot;bg-white&quot;&gt;
      &lt;StatusBar barStyle={&#39;light-content&#39;} backgroundColor={&#39;#fff&#39;} /&gt;
      &lt;Pressable
        onPress={goToDetailMenu}
        className=&quot;bg-green-300 p-4 text-green-900 m-10 border border-solid border-green-900 rounded&quot;&gt;
        &lt;Text&gt;Home&lt;/Text&gt;
      &lt;/Pressable&gt;
    &lt;/SafeAreaView&gt;
  );
};

export default HomeScreen;</code></pre>
<p>훅으로 분리</p>
<p><strong>hooks/useNavi.ts</strong></p>
<pre><code class="language-ts">import { Platform } from &#39;react-native&#39;;
import { useNavigate } from &#39;react-router-dom&#39;;
import { useNavigation } from &#39;@react-navigation/native&#39;;
import { NativeStackNavigationProp } from &#39;@react-navigation/native-stack&#39;;

const useNavi = () =&gt; {
  const isWeb = Platform.OS === &#39;web&#39;;

  const navigate = isWeb ? useNavigate() : undefined;
  const navigation = isWeb ? undefined : useNavigation&lt;NativeStackNavigationProp&lt;any&gt;&gt;();

  return {
    navigate, // 웹
    navigation, // 앱
  };
};

export default useNavi;</code></pre>
<p><br><br>
<strong>+</strong>
아직 web을 지원하지 않는 라이브러리들이 많고,
지원하더라도 web에서 제대로 동작하지 않는 경우가 많다.
그래서 가능한 한 웹은 별도로 개발하는 게 좋을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[GitHub] 🐈‍⬛Contributors 삭제하는 방법]]></title>
            <link>https://velog.io/@tata-v_vlelog/GitHub-Contributors-%EC%82%AD%EC%A0%9C%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@tata-v_vlelog/GitHub-Contributors-%EC%82%AD%EC%A0%9C%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Tue, 04 Jun 2024 16:05:50 GMT</pubDate>
            <description><![CDATA[<h2 id="▷-contributors-삭제하는-방법">▷ Contributors 삭제하는 방법</h2>
<h3 id="⒈-현재-username과-useremail-확인하기">⒈ 현재 user.name과 user.email 확인하기</h3>
<pre><code class="language-bash"># global
git config --global user.name
git config --global user.email

git config user.name
git config user.email</code></pre>
<hr>
<h3 id="⒉-로그-확인-삭제">⒉ 로그 확인, 삭제</h3>
<pre><code class="language-bash"># 로그 확인
git log

# 두 개의 최근 커밋을 삭제
git reset --hard HEAD~2 # 로컬의 내용도 변경
# git reset --soft HEAD~2 # 로컬의 변경 내용을 그대로 유지
# git stash # 변경사항을 임시로 저장

# 이전 커밋으로 강제 푸쉬
git push --force origin main
# git stash pop # 변경사항을 다시 적용</code></pre>
<p>또는</p>
<pre><code class="language-bash"># 프로젝트 내에 특정한 계정으로 되어있는 커밋이 있는지 확인
git log --author=&quot;사용자이름&quot;

# commit hash 값 -&gt; 삭제하고 싶은 첫번째 commit 바로 직전의 commit hash.
# 열린 git rebase editor에 삭제하고 싶은 commit들을 pick -&gt; drop으로 변경
git rebase -i &lt;commit hash&gt;

# 이전 커밋으로 강제 푸쉬
git push --force origin main</code></pre>
<hr>
<h3 id="⒊-브랜치-이름-변경으로-contributor-삭제">⒊ 브랜치 이름 변경으로 Contributor 삭제</h3>
<p>지우고 싶은 contributor의 commit들을 지웠지만,
레파지토리의 우측 contributor목록에는 아직 계정이 존재함.</p>
<p><code>View all branches</code> <strong>→</strong> <code>Rename branch</code> 에서
<code>main</code> 브랜치 이름을 <code>main1</code> 로 변경 후, 다시 <code>main</code>으로 변경하면 contributor가 삭제된다.</p>
<p><img src="https://velog.velcdn.com/images/tata-v_vlelog/post/d0efe3a6-622c-45d0-a5ba-6dd730c58fc8/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/tata-v_vlelog/post/e6f8c1d2-b580-46f7-92d9-f40672aa0eb4/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RN] 🧶 커스텀 폰트 적용하기 (otf, ttf 파일)]]></title>
            <link>https://velog.io/@tata-v_vlelog/RN-Font-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@tata-v_vlelog/RN-Font-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 17 May 2024 13:59:03 GMT</pubDate>
            <description><![CDATA[<h2 id="▷-react-native에-font-적용하기">▷ React-Native에 Font 적용하기</h2>
<p>src/assets/fonts에 원하는 폰트의 ttf파일들 넣기</p>
<p><img src="https://velog.velcdn.com/images/tata-v_vlelog/post/bb8b473f-816a-47aa-8cfd-0a592b092ab0/image.png" alt=""></p>
<p>react-native.config.js</p>
<pre><code class="language-js">module.exports = {
  assets: [&#39;src/assets/fonts&#39;],
};</code></pre>
<p>터미널에 아래 명령어 입력</p>
<pre><code class="language-bash">npx react-native-asset</code></pre>
<p>android 에서는 android/app/src/main/assets/fonts에 폰트들이 잘 생성되었는지 확인하고,
ios 에서는 Info.plist 파일에 UIAppFonts가 제대로 들어갔는지 확인하면 된다.</p>
<pre><code class="language-tsx">// 폰트 적용하기
&lt;Text style={{fontFamily: &#39;NanumSquareNeo-dEb&#39;}}&gt;
  tata-v
&lt;/Text&gt;</code></pre>
<hr>
<h3 id="🧶-tailwind에-적용하기">🧶 tailwind에 적용하기</h3>
<p>tailwind.config.js</p>
<pre><code class="language-ts">module.exports = {
  content: [&#39;./App.{js,jsx,ts,tsx}&#39;, &#39;./src/**/*.{js,jsx,ts,tsx}&#39;, &#39;./public/**/*.{js,jsx,ts,tsx,html}&#39;],
  theme: {
    fontFamily: {
      PTDBlack: [&#39;Pretendard-Black&#39;],
      PTDBold: [&#39;Pretendard-Bold&#39;],
      PTDExtraBold: [&#39;Pretendard-ExtraBold&#39;],
      PTDExtraLight: [&#39;Pretendard-ExtraLight&#39;],
      PTDLight: [&#39;Pretendard-Light&#39;],
      PTDMedium: [&#39;Pretendard-Medium&#39;],
      PTDRegular: [&#39;Pretendard-Regular&#39;],
      PTDSemiBold: [&#39;Pretendard-SemiBold&#39;],
      PTDThin: [&#39;Pretendard-Thin&#39;],
      UhBeeBold: [&#39;UhBee-Bold&#39;],
      UhBee: [&#39;UhBee&#39;],
    },
  },
  ...</code></pre>
<p>적용하는 곳</p>
<pre><code class="language-tsx">&lt;Text className=&quot;font-PTDBlack&quot;&gt;Go To SignIn&lt;/Text&gt;</code></pre>
<hr>
<h3 id="🧶-stylesheet에-적용하기">🧶 StyleSheet에 적용하기</h3>
<p>src/style/font.style.js</p>
<pre><code class="language-ts">import { StyleSheet } from &#39;react-native&#39;;

export const font = StyleSheet.create({
  nanum400: {
    fontFamily: &#39;NanumSquareNeo-aLt&#39;,
  },
  nanum500: {
    fontFamily: &#39;NanumSquareNeo-bRg&#39;,
  },
  nanum700: {
    fontFamily: &#39;NanumSquareNeo-cBd&#39;,
  },
  nanum800: {
    fontFamily: &#39;NanumSquareNeo-dEb&#39;,
  },
  nanum900: {
    fontFamily: &#39;NanumSquareNeo-eHv&#39;,
  },
});</code></pre>
<p>적용하는 곳</p>
<pre><code class="language-tsx">&lt;Text style={font.nanum800} className=&quot;mt-[32px]  text-gray900 text-xl&quot;&gt;
  tata-v
&lt;/Text&gt;</code></pre>
<p>커스텀 Text 컴포넌트</p>
<pre><code class="language-tsx">import React from &#39;react&#39;;
import { Text, TextProps } from &#39;react-native&#39;;

interface CustomTextProps extends TextProps {}

const CustomText: React.FC&lt;CustomTextProps&gt; = ({ style, ...rest }) =&gt; {
  const customStyle = {
    fontFamily: &#39;Pretendard-Regular&#39;,
    color: &#39;#000000&#39;
  };

  return &lt;Text style={[customStyle, style]} {...rest} /&gt;;
};</code></pre>
<hr>
<h3 id="❗️-만약-ios에-폰트가-적용되지-않는다면">❗️ 만약 ios에 폰트가 적용되지 않는다면</h3>
<p>src/assets/fonts에 저장된 폰트 파일의 이름이 PostScript 이름과 일치하지 않으면 폰트가 제대로 적용되지 않는다.</p>
<p>폰트 파일 이름을 PostScript 이름과 동일하게 변경해야 한다.</p>
<p><img src="https://velog.velcdn.com/images/tata-v_vlelog/post/9e1c4a2d-cd2b-468f-8b66-18b7dbea32e9/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Vue] 🎷 Lottie 사용법]]></title>
            <link>https://velog.io/@tata-v_vlelog/Vue-Lottie-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@tata-v_vlelog/Vue-Lottie-%EC%82%AC%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Sat, 02 Dec 2023 18:36:18 GMT</pubDate>
            <description><![CDATA[<h2 id="▷-lottie">▷ Lottie</h2>
<p>JSON 형식으로 애니메이션을 저장하여 비교적 파일 크기가 작은 Lottie.
<a href="https://lottiefiles.com/">Lottie</a>에서 원하는 파일을 다운로드 받아 사용할 수 있다.
<a href="https://app.lottiefiles.com/preview">받아온 Lottie 파일을 미리 플레이해 볼 수 있는 곳</a></p>
<p><img src="https://velog.velcdn.com/images/tata-v_vlelog/post/94c63153-b9db-47c9-837e-c882784cc321/image.png" alt=""></p>
<hr>
<h3 id="🎷-설치">🎷 설치</h3>
<pre><code class="language-bash"># 설치
npm install vue3-lottie@latest --save
# yarn add vue3-lottie@latest
# pnpm add vue3-lottie@latest</code></pre>
<hr>
<h3 id="🎷-vue3에서-lottie-설정하기">🎷 Vue3에서 Lottie 설정하기</h3>
<p><a href="https://vue3-lottie.vercel.app/introduction/vue-3">Usage with Vue 3</a>
<a href="https://vue3-lottie.vercel.app/introduction/nuxt-3#usage-with-nuxt-3">Usage with Nuxt 3</a></p>
<pre><code class="language-jsx">/* main.js */
import { createApp } from &#39;vue&#39;
import Vue3Lottie from &#39;vue3-lottie&#39;

createApp(App)
  .use(Vue3Lottie, { name: &#39;LottieAnimation&#39; })
  .mount(&#39;#app&#39;)</code></pre>
<pre><code class="language-jsx">/* components.d.ts */
declare module &#39;@vue/runtime-core&#39; {
  export interface GlobalComponents {
    LottieAnimation: typeof import(&#39;vue3-lottie&#39;)[&#39;Vue3Lottie&#39;]
  }
}</code></pre>
<hr>
<h3 id="🎷-lottie-사용하기">🎷 Lottie 사용하기</h3>
<pre><code class="language-jsx">&lt;script setup lang=&quot;ts&quot;&gt;
import Lottie from &#39;vue-lottie&#39;;
import listLoadingJson from &#39;src/assets/json/list_loading.json&#39;;
&lt;/script&gt;

&lt;template&gt;
&lt;Vue3Lottie
  :animationData=&quot;listLoadingJson&quot;
  :height=&quot;200&quot;
  :width=&quot;200&quot;
/&gt;
&lt;/template&gt;</code></pre>
<p><br><br></p>
<p>👉 <a href="https://vue3-lottie.vercel.app/introduction/installation">vue3 Lottie</a>
👉 <a href="https://github.com/airbnb/lottie-web">lottie-web으로 Lottie에 다양한 옵션 추가하기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Vue] Vue3 🍍pinia 사용법]]></title>
            <link>https://velog.io/@tata-v_vlelog/Vue-Vue3-pinia-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@tata-v_vlelog/Vue-Vue3-pinia-%EC%82%AC%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Tue, 24 Oct 2023 01:47:08 GMT</pubDate>
            <description><![CDATA[<h2 id="▷-pinia">▷ pinia</h2>
<p>vue3에서 전역 상태 관리를 할 수 있는 pinia</p>
<h3 id="🍍-설치">🍍 설치</h3>
<pre><code class="language-bash">npm install pinia</code></pre>
<hr>
<h3 id="🍍-pinia-설정">🍍 pinia 설정</h3>
<p>main.js</p>
<pre><code class="language-js">import { createPinia } from &#39;pinia&#39;;

const app = createApp(App);
app.use(createPinia());</code></pre>
<p>src/store/counter.js</p>
<pre><code class="language-js">// stores/counter.js
import { defineStore } from &#39;pinia&#39;;

export const useCounterStore = defineStore(&#39;counter&#39;, {
  // 초기 상태 지정
  state: () =&gt; {
    return { count: 1 };
  },
  getters: {
    doubleCount: state =&gt; state.count * 2,
  },
  // 액션 함수 지정
  actions: {
    increment() {
      this.count++;
    },
  },
});</code></pre>
<hr>
<h3 id="🍍-setup-stores">🍍 Setup Stores</h3>
<p><a href="https://pinia.vuejs.kr/core-concepts/">Setup Stores</a></p>
<p><code>ref()</code>는 <code>state</code> 속성이 됨.
<code>computed()</code>는 <code>getters</code>가 됨.
<code>function()</code>은 <code>actions</code>가 됨.</p>
<pre><code class="language-js">export const useCounterStore = defineStore(&#39;counter&#39;, () =&gt; {
  const count = ref(0)
  const name = ref(&#39;Eduardo&#39;)
  const doubleCount = computed(() =&gt; count.value * 2)
  function increment() {
    count.value++
  }

  return { count, name, doubleCount, increment }
})</code></pre>
<hr>
<h3 id="🍍-상태-가져와서-사용하기">🍍 상태 가져와서 사용하기</h3>
<pre><code class="language-vue">&lt;template&gt;
  &lt;div&gt;
    &lt;p&gt;Count: {{ store.count }}&lt;/p&gt;
    &lt;p&gt;Count: {{ store.doubleCount }}&lt;/p&gt;
    &lt;button @:click=&quot;store.increment()&quot;&gt;&lt;/button&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { useCounterStore } from &#39;@/store/counter&#39;;

const store = useCounterStore();
&lt;/script&gt;</code></pre>
<p>➕ typescript</p>
<pre><code class="language-js">import { defineStore } from &#39;pinia&#39;;

interface Counter {
  count: number;
}

export const useCounterStore = defineStore(&#39;counter&#39;, {
  state: () =&gt; {
    return { count: 1 } as Counter;
  },
  actions: {
    increament() {
      this.count++;
    },
  },
});</code></pre>
<hr>
<h3 id="🍍-storetorefs">🍍 storeToRefs</h3>
<p>store에서 그냥 구조 분해를 하면 해당 객체가 store와 떨어지게 된다.
storeToRefs을 써서 구조 분해를 하면 반응성을 유지할 수 있다.</p>
<pre><code class="language-js">import { useCounterStore } from &#39;@/store/counter&#39;;
const store = useCounterStore();
// const { counter } = store;
const { counter } = storeToRefs(store);
const { increment } = store; // 함수는 그냥 가져오기 가능</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 📕Recoil 로컬, 세션 스토리지 (ft. 만료시간 지정)]]></title>
            <link>https://velog.io/@tata-v_vlelog/React-Recoil-%EB%A1%9C%EC%BB%AC-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80-ft.-%EB%A7%8C%EB%A3%8C%EC%8B%9C%EA%B0%84-%EC%A7%80%EC%A0%95</link>
            <guid>https://velog.io/@tata-v_vlelog/React-Recoil-%EB%A1%9C%EC%BB%AC-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80-ft.-%EB%A7%8C%EB%A3%8C%EC%8B%9C%EA%B0%84-%EC%A7%80%EC%A0%95</guid>
            <pubDate>Thu, 05 Oct 2023 07:13:22 GMT</pubDate>
            <description><![CDATA[<h3 id="📕-recoil-로컬-스토리지">📕 Recoil 로컬 스토리지</h3>
<p><a href="https://recoiljs.org/docs/guides/atom-effects/"><strong>Local Storage</strong></a></p>
<pre><code class="language-jsx">const localStorageEffect = key =&gt; ({setSelf, onSet}) =&gt; {
  const savedValue = localStorage.getItem(key)
  if (savedValue != null) {
    setSelf(JSON.parse(savedValue));
  }

  onSet((newValue, _, isReset) =&gt; {
    isReset
      ? localStorage.removeItem(key)
      : localStorage.setItem(key, JSON.stringify(newValue));
  });
};</code></pre>
<p>typescript 추가</p>
<pre><code class="language-jsx">import { AtomEffect } from &#39;recoil&#39;;

const localStorageEffect: &lt;T&gt;(key: string) =&gt; AtomEffect&lt;T&gt; =
  (key: string) =&gt;
  ({ setSelf, onSet }) =&gt; {
    const savedValue = localStorage.getItem(key);
    if (savedValue != null) {
      setSelf(JSON.parse(savedValue));
    }

    onSet((newValue, _, isReset) =&gt; {
      isReset ? localStorage.removeItem(key) : localStorage.setItem(key, JSON.stringify(newValue));
    });
  };

export default localStorageEffect;</code></pre>
<p>원하는 atom에 적용</p>
<pre><code class="language-jsx">const currentUserIDState = atom({
  key: &#39;CurrentUserID&#39;,
  default: 1,
  effects: [
    localStorageEffect(&#39;current_user&#39;), // ⭐️추가
  ]
});</code></pre>
<hr>
<h3 id="📕-로컬스토리지-만료-시간-지정하기">📕 로컬스토리지 만료 시간 지정하기</h3>
<pre><code class="language-jsx">import { AtomEffect } from &#39;recoil&#39;;

// 원하는 시간 지정
const ONE_HOUR = 3600 * 1000; // 1시간
const getExpirationTime = () =&gt; Date.now() + ONE_HOUR;

const localStorageEffect: &lt;T&gt;(key: string) =&gt; AtomEffect&lt;T&gt; =
  (key: string) =&gt;
  ({ setSelf, onSet }) =&gt; {
    // 로컬에 저장된 키 가져오기
    const savedValue = localStorage.getItem(key);
    const expirationTime = localStorage.getItem(`${key}_expiration`);

    // 만료 시간이 지났거나 값이 없을 경우 삭제
    if (expirationTime &amp;&amp; Date.now() &gt; parseInt(expirationTime, 10)) {
      localStorage.removeItem(key); // 값 삭제
      localStorage.removeItem(`${key}_expiration`); // 만료 시간 정보 삭제
    } else if (savedValue != null) {
      // 만료 시간이 지나지 않았고, 값이 존재하는 경우에는 해당 값을 Recoil 상태에 설정
      setSelf(JSON.parse(savedValue));
    }

    // Recoil 상태가 변경될 때 호출되는 콜백 함수를 정의
    onSet((newValue, _, isReset) =&gt; {
      // Recoil 상태가 재설정되는 경우, 해당 키와 만료 시간 정보를 삭제
      if (isReset) {
        localStorage.removeItem(key);
        localStorage.removeItem(`${key}_expiration`);
      } else {
        // Recoil 상태가 업데이트되는 경우, 새로운 값을 로컬 스토리지에 저장하고 만료 시간 정보를 갱신
        localStorage.setItem(key, JSON.stringify(newValue)); // 값 저장
        localStorage.setItem(`${key}_expiration`, getExpirationTime().toString()); // 만료 시간 갱신
      }
    });
  };

export default localStorageEffect;</code></pre>
<hr>
<h2 id="▷-recoil-persist">▷ recoil-persist</h2>
<h3 id="📕-세션스토리지">📕 세션스토리지</h3>
<p>recoil-persist로 sessionStorage 또는 localStorage를 사용할 수 있다.</p>
<pre><code class="language-ts">import { atom } from &#39;recoil&#39;;
import { recoilPersist } from &#39;recoil-persist&#39;;

const sessionStorage = 
      typeof window !== &#39;undefined&#39; ? window.sessionStorage : undefined

const { persistAtom } = recoilPersist({
  key: &#39;music&#39;,
  storage: sessionStorage,
});

const musicAtom = atom&lt;MyAtomType&gt;({
  key: &#39;musicAtom&#39;,
  default: myDefaultState,
  effects_UNSTABLE: [persistAtom], // ⭐️ 추가
});</code></pre>
<h3 id="📕-ssr-대응">📕 SSR 대응</h3>
<p><a href="https://github.com/polemius/recoil-persist#server-side-rendering">SSR 대응법
</a></p>
<pre><code class="language-ts">/* hook */
import { useEffect, useState } from &#39;react&#39;;
import { useRecoilState } from &#39;recoil&#39;;
import currentTrackState, { currentTrackDefault } from &#39;src/atom/currentTrackState&#39;;

const useCurrentTrackSSR = () =&gt; {
  const [isInitial, setIsInitial] = useState(true);
  const [value, setValue] = useRecoilState(currentTrackState);

  useEffect(() =&gt; {
    setIsInitial(false);
  }, []);

  return [isInitial ? currentTrackDefault : value, setValue] as const;
};

export default useCurrentTrackSSR;</code></pre>
<pre><code class="language-ts">/* 최상위에서 한번만 사용 */
const [currentMusicAndTrack, setCurrentMusicAndTrack] = useCurrentTrackSSR();</code></pre>
<p><br><br>
🍒<a href="https://velog.io/@tata-v_vlelog/React-Recoil-%EC%82%AC%EC%9A%A9%EB%B2%95">Recoil 사용법 보러가기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 🎷Lottie 사용법]]></title>
            <link>https://velog.io/@tata-v_vlelog/React-Lottie-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@tata-v_vlelog/React-Lottie-%EC%82%AC%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Tue, 26 Sep 2023 03:14:58 GMT</pubDate>
            <description><![CDATA[<h2 id="▷-lottie">▷ Lottie</h2>
<p>JSON 형식으로 애니메이션을 저장하여 비교적 파일 크기가 작은 Lottie.
<a href="https://lottiefiles.com/">Lottie</a>에서 원하는 파일을 다운로드 받아 사용할 수 있다.
<a href="https://app.lottiefiles.com/preview">받아온 Lottie 파일을 미리 플레이해 볼 수 있는 곳</a></p>
<p><img src="https://velog.velcdn.com/images/tata-v_vlelog/post/94c63153-b9db-47c9-837e-c882784cc321/image.png" alt=""></p>
<pre><code class="language-bash"># 설치
npm install --save react-lottie-player</code></pre>
<p>(Next.js라면 json파일은 public폴더에 넣고 사용)</p>
<pre><code class="language-jsx">import Lottie from &#39;react-lottie-player&#39;;
import loadingJson from &#39;../public/lottie/animation_loading.json&#39;;

function LoadingLottie() {
  return (
    &lt;Lottie loop animationData={loadingJson} play /&gt;
  );
}

export default LoadingLottie;</code></pre>
<p>Next.js라면 dynamic import로 가져와서 사용해야 함</p>
<pre><code class="language-tsx">import { CSSProperties } from &#39;react&#39;;
import dynamic from &#39;next/dynamic&#39;;
import loadingJson from &#39;@/public/json/loading.json&#39;;

interface Props {
  customStyle?: CSSProperties;
}

function LoadingLottie({ customStyle }: Props) {
  const Lottie = dynamic(() =&gt; import(&#39;react-lottie-player&#39;), { ssr: false });

  return (
    &lt;Lottie style={customStyle} loop animationData={loadingJson} play /&gt;
  );
}

export default LoadingLottie;</code></pre>
<p>공홈에서 json 파일을 다운로드 받아 사용할 수도 있지만,
직접 만든 애니메이션을 Lottie로 사용하고 사용하고 싶다면
👉 <a href="https://ourbooklist.tistory.com/entry/%EC%97%90%ED%8E%99-%EB%A1%9C%ED%8B%B0-%EB%A7%A5">에펙에서 로티 애니매이션 만들기1</a>
👉 <a href="https://brunch.co.kr/@pizzakim/46">에펙에서 로티 애니매이션 만들기2</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RN] 🪶커스텀 Icon Font 사용법 (expo)]]></title>
            <link>https://velog.io/@tata-v_vlelog/RN-%EC%BB%A4%EC%8A%A4%ED%85%80-Icon-Font-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-expo</link>
            <guid>https://velog.io/@tata-v_vlelog/RN-%EC%BB%A4%EC%8A%A4%ED%85%80-Icon-Font-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-expo</guid>
            <pubDate>Wed, 06 Sep 2023 02:43:54 GMT</pubDate>
            <description><![CDATA[<h2 id="▷-커스텀-icon-font-사용법-expo">▷ 커스텀 Icon Font 사용법 (expo)</h2>
<h3 id="🪶-span-stylebackground-colorf9f1e0icomoon에서-svg를-icon-폰트로-바꾸기span">🪶 <span style="background-color:#f9f1e0">Icomoon에서 svg를 icon 폰트로 바꾸기</span></h3>
<p>SVG 이미지를 Icon Font로 변환해주는 사이트
<strong>→</strong> <a href="https://icomoon.io/app/#/select">https://icomoon.io/app/#/select</a></p>
<p>SVG 이미지를 Icon Font로 변환하는 방법
<strong>→</strong> <a href="https://velog.io/@tata-v_vlelog/HTML-SVG%EB%A5%BC-Icon-Font%EB%A1%9C-%EB%B0%94%EA%BE%B8%EA%B8%B0">SVG를 Icon Font로 바꾸는 방법(웹)</a></p>
<p>icomoon을 사용하여 아이콘을 생성한 후 다운로드하기 전에
설정에서 CSS Selector를 &#39;Use a class&#39;로 변경해야 한다.</p>
<p><img src="https://velog.velcdn.com/images/tata-v_vlelog/post/d8df14ba-20bc-482e-b69b-652a903160b2/image.png" alt=""></p>
<p>다운로드한 파일 중에서 <code>icomoon.ttf</code>와 <code>selection.json</code> 파일만 필요하다.
이 파일들을 아래 경로로 저장해 주었다.</p>
<p>src/assets/fonts/icomoon.ttf
src/assets/selection.json</p>
<hr>
<h3 id="🪶-span-stylebackground-colorf9f1e0icon-font-폰트-적용하기span">🪶 <span style="background-color:#f9f1e0">Icon Font 폰트 적용하기</span></h3>
<p>react-native-vector-icons 설치</p>
<pre><code class="language-bash"># 설치
npm install --save react-native-vector-icons
npm install --save-dev @types/react-native-vector-icons</code></pre>
<p>icomoon.ttf를 폰트로 지정</p>
<p>App.tsx</p>
<pre><code class="language-tsx">/* App.tsx */
useEffect(() =&gt; {
  async function loadFonts() {
    await Font.loadAsync({
      customIcons: require(&#39;./src/assets/fonts/icomoon.ttf&#39;), // ⭐️폰트 지정
    });
    setFontsLoaded(true);
  }

  loadFonts();
}, []);

if (!fontsLoaded) {
  return null; // 폰트 로딩 중에는 렌더링을 방지
}</code></pre>
<p>src/components/Icon/Icon.tsx</p>
<p><code>createIconSetFromIcoMoon(selection.json, icomoon.ttf폰트)</code></p>
<pre><code class="language-tsx">/* src/components/Icon/Icon.tsx */
import icoMoonConfig from &#39;../../assets/selection.json&#39;;
import { createIconSetFromIcoMoon } from &#39;react-native-vector-icons&#39;;

const Icon = createIconSetFromIcoMoon(icoMoonConfig, &#39;customIcons&#39;);

export default Icon;</code></pre>
<p>이제 아래처럼 커스텀 아이콘 폰트를 사용할 수 있다.
<code>&lt;Icon name=&quot;아이콘폰트이름&quot; size={30} color=&quot;blue&quot; /&gt;</code></p>
<pre><code class="language-tsx">import Icon from &#39;./src/components/Icon/Icon&#39;;

&lt;Icon name=&quot;moon&quot; size={30} /&gt;</code></pre>
<p><br><br></p>
<p>👉 <a href="https://www.orangejellyfish.com/blog/custom-icon-fonts-with-react-native/">Custom icon fonts with React Native</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RN]  🌻svg를 컴포넌트로 사용하기]]></title>
            <link>https://velog.io/@tata-v_vlelog/RN-%EC%BB%A4%EC%8A%A4%ED%85%80-icon%ED%8F%B0%ED%8A%B8-%EC%82%AC%EC%9A%A9%EB%B2%95ft.-svg-%EC%9D%B4%EB%AF%B8%EC%A7%80</link>
            <guid>https://velog.io/@tata-v_vlelog/RN-%EC%BB%A4%EC%8A%A4%ED%85%80-icon%ED%8F%B0%ED%8A%B8-%EC%82%AC%EC%9A%A9%EB%B2%95ft.-svg-%EC%9D%B4%EB%AF%B8%EC%A7%80</guid>
            <pubDate>Tue, 05 Sep 2023 16:21:18 GMT</pubDate>
            <description><![CDATA[<h3 id="⒈-react-native-svg-설치">⒈ react-native-svg 설치</h3>
<pre><code class="language-bash"># 설치
npm install react-native-svg
# Expo일 경우 ↓
# expo install react-native-svg

npm install -D react-native-svg-transformer</code></pre>
<hr>
<h3 id="⒉-metroconfigts">⒉ metro.config.ts</h3>
<p><a href="https://www.npmjs.com/package/react-native-svg-transformer"><strong>metro.config.ts</strong></a> 파일 생성</p>
<p>cli</p>
<pre><code class="language-ts">const { getDefaultConfig, mergeConfig } = require(&quot;@react-native/metro-config&quot;);

const defaultConfig = getDefaultConfig(__dirname);
const { assetExts, sourceExts } = defaultConfig.resolver;

/**
 * Metro configuration
 * https://reactnative.dev/docs/metro
 *
 * @type {import(&#39;metro-config&#39;).MetroConfig}
 */
const config = {
  transformer: {
    babelTransformerPath: require.resolve(&quot;react-native-svg-transformer&quot;)
  },
  resolver: {
    assetExts: assetExts.filter((ext) =&gt; ext !== &quot;svg&quot;),
    sourceExts: [...sourceExts, &quot;svg&quot;]
  }
};

module.exports = mergeConfig(defaultConfig, config);</code></pre>
<p>expo</p>
<pre><code class="language-js">/* metro.config.ts */
const { getDefaultConfig } = require(&quot;expo/metro-config&quot;);

module.exports = (() =&gt; {
  const config = getDefaultConfig(__dirname);

  const { transformer, resolver } = config;

  config.transformer = {
    ...transformer,
    babelTransformerPath: require.resolve(&quot;react-native-svg-transformer&quot;),
  };
  config.resolver = {
    ...resolver,
    assetExts: resolver.assetExts.filter((ext: string) =&gt; ext !== &quot;svg&quot;),
    sourceExts: [...resolver.sourceExts, &quot;svg&quot;],
  };

  return config;
})();</code></pre>
<p>global.d.ts</p>
<pre><code class="language-ts">declare module &#39;*.svg&#39; {
  import React from &#39;react&#39;;
  import {SvgProps} from &#39;react-native-svg&#39;;
  const content: React.FC&lt;SvgProps&gt;;
  export default content;
}</code></pre>
<p>src/assets/<del>~</del>.svg</p>
<p>svg파일 넣어두고 import해서 컴포넌트처럼 사용하면 됨</p>
<hr>
<h3 id="➕-이미지-가져오기">➕ 이미지 가져오기</h3>
<p>require(&#39;경로&#39;)로 이미지를 가져올 수도 있지만</p>
<pre><code class="language-jsx">&lt;Image source={require(&#39;./src/assets/png-file/crayon-line.png&#39;)} /&gt;</code></pre>
<p>아래처럼 사용하고 싶다면</p>
<pre><code class="language-jsx">import crayonPng from &#39;./src/assets/png-file/crayon-line.png&#39;;

&lt;Image source={crayonPng} /&gt;</code></pre>
<p>import-image.d.ts 파일을 생성하고,
이미지 파일 확장자인 .png 및 .jpg에 대한 모듈 타입을 선언하면 된다.</p>
<pre><code class="language-js">/* import-image.d.ts */
declare module &#39;*.png&#39;;
declare module &#39;*.jpg&#39;;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RN] 📝expo 폰트 변경하기 (ft. global-props)]]></title>
            <link>https://velog.io/@tata-v_vlelog/RN-expo-%ED%8F%B0%ED%8A%B8-%EB%B3%80%EA%B2%BD%ED%95%98%EA%B8%B0-ft.-react-native-global-props</link>
            <guid>https://velog.io/@tata-v_vlelog/RN-expo-%ED%8F%B0%ED%8A%B8-%EB%B3%80%EA%B2%BD%ED%95%98%EA%B8%B0-ft.-react-native-global-props</guid>
            <pubDate>Mon, 04 Sep 2023 14:11:33 GMT</pubDate>
            <description><![CDATA[<h2 id="▷-react-native-expo-폰트-변경">▷ react-native expo 폰트 변경</h2>
<h3 id="⒈-ttf파일">⒈ ttf파일</h3>
<p><img src="https://velog.velcdn.com/images/tata-v_vlelog/post/53dc599f-9509-4cd6-a96b-89a4b4187252/image.png" alt=""></p>
<p>구글 폰트에서 원하는 폰트를 다운로드 받아 ttf파일을 assets/fonts 파일 안에 넣어준다.</p>
<hr>
<h3 id="⒉-expo-font-설치">⒉ expo-font 설치</h3>
<pre><code class="language-bash"># 설치
npm install expo-font</code></pre>
<hr>
<h3 id="⒊-폰트-적용하기">⒊ 폰트 적용하기</h3>
<p>폰트 로딩의 기본 사용법: <code>Font.loadAsync({&quot;폰트명&quot;: require(&quot;경로&quot;)});</code></p>
<p>폰트 로딩은 비동기 작업이기 때문에 렌더링이 시작된 후에도 폰트 로딩이 완료되지 않을 수 있다. 그러므로 useEffect 함수를 사용해서 폰트 로딩이 완료되면 로딩 상태를 true로 변경해줘야 한다. 이렇게 하면 폰트 로딩이 완료된 후에만 텍스트 스타일에 폰트를 적용할 수 있다.(안 해주면 오류 뜸)</p>
<pre><code class="language-js">import * as Font from &#39;expo-font&#39;;
import { useEffect, useState } from &#39;react&#39;;

export default function App() {
  const [fontsLoaded, setFontsLoaded] = useState(false);

  useEffect(() =&gt; {
    async function loadFonts() {
      await Font.loadAsync({
        bubblegum: require(&#39;./src/assets/fonts/BubblegumSans-Regular.ttf&#39;),
        notosans: require(&#39;./src/assets/fonts/NotoSansKR-VariableFont_wght.ttf&#39;),
      });
      setFontsLoaded(true);
    }

    loadFonts();
  }, []);

  if (!fontsLoaded) {
    return null; // 폰트 로딩 중에는 렌더링을 방지
  }

  return (
    &lt;SafeAreaView&gt;
      // 적용 방법
      &lt;Text style={{ fontFamily: &#39;notosans&#39;, fontSize: 28 }}&gt;Font&lt;/Text&gt;
      &lt;Text style={{ fontFamily: &#39;bubblegum&#39;, fontSize: 28 }}&gt;Font&lt;/Text&gt;
    &lt;/SafeAreaView&gt;
  );
}</code></pre>
<hr>
<h2 id="▷-react-native-global-props">▷ react-native-global-props</h2>
<p>notosans 폰트를 모든 Text에 개별적으로 설정하는 대신, 전역으로 한 번만 설정하여 모든 텍스트에 일괄적으로 적용하고 싶었다.</p>
<p>그럴 땐 <a href="https://www.npmjs.com/package/react-native-global-props">react-native-global-props</a> 라이브러리를 사용하면 된다.</p>
<h3 id="⒈-설치">⒈ 설치</h3>
<pre><code class="language-bash"># 설치
npm i react-native-global-props --save
npm install --save-dev @types/react-native-global-props</code></pre>
<h3 id="⒉-setcustomtext">⒉ setCustomText</h3>
<p>setCustomText로 폰트를 적용</p>
<pre><code class="language-js">import { setCustomText } from &#39;react-native-global-props&#39;;

const customTextProps = {
  style: {
    fontFamily: &#39;notosans&#39;,
  },
};

setCustomText(customTextProps);</code></pre>
<p>전체 코드</p>
<pre><code class="language-js">import * as Font from &#39;expo-font&#39;;
import { useEffect, useState } from &#39;react&#39;;
import { setCustomText } from &#39;react-native-global-props&#39;;

export default function App() {
  const [fontsLoaded, setFontsLoaded] = useState(false);

  useEffect(() =&gt; {
    async function loadFonts() {
      await Font.loadAsync({
        bubblegum: require(&#39;./src/assets/fonts/BubblegumSans-Regular.ttf&#39;),
        notosans: require(&#39;./src/assets/fonts/NotoSansKR-VariableFont_wght.ttf&#39;),
      });
      setFontsLoaded(true);
    }

    loadFonts();
  }, []);

  if (!fontsLoaded) {
    return null; // 폰트 로딩 중에는 렌더링을 방지
  }

  const customTextProps = {
    style: {
      fontFamily: &#39;notosans&#39;,
    },
  };
  setCustomText(customTextProps);

  return (
    &lt;SafeAreaView&gt;
      &lt;Text style={{ fontSize: 28 }}&gt;Font&lt;/Text&gt; // ⭐️fontFamily: &#39;notosans&#39;를 해주지 않아도 적용됨
      &lt;Text style={{ fontFamily: &#39;bubblegum&#39;, fontSize: 28 }}&gt;Font&lt;/Text&gt;
    &lt;/SafeAreaView&gt;
  );
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RN] 🪸Animated로 애니메이션 적용하기]]></title>
            <link>https://velog.io/@tata-v_vlelog/RN-Animated%EB%A1%9C-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@tata-v_vlelog/RN-Animated%EB%A1%9C-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 05 Aug 2023 08:33:59 GMT</pubDate>
            <description><![CDATA[<h2 id="▷-animated">▷ Animated</h2>
<p>Animated는 애니메이션을 쉽게 만들 수 있도록 도와준다.</p>
<p>컴포넌트의 스타일, 위치, 투명도 등의 속성을 애니메이션으로 변경하고, 애니메이션 시간, 타이밍, 반복 등을 세밀하게 제어할 수 있다.</p>
<hr>
<h3 id="🪸-animated-value-생성">🪸 Animated value 생성</h3>
<pre><code class="language-jsx">// 값을 직접 바꾸면 안되기에 useRef를 사용함
// Value의 생성자 함수 인자에는 초깃값을 넣어줌
const refAnimation = useRef(new Animated.Value(0)).current;</code></pre>
<hr>
<h3 id="🪸-interpolate-animatedview">🪸 interpolate, Animated.View</h3>
<p><code>interpolate</code>: Value가 가지고 있는 값을 기준으로 새로운 값을 생성</p>
<pre><code class="language-jsx">const widthAnimation = refAnimation.interpolate({
  inputRange: [0, 10], // refAnimation의 값이 0에서 10 사이로 변화할 수 있음
  outputRange: [&#39;0%&#39;, &#39;100%&#39;], // refAnimation이 0일 때는 &#39;0%&#39;가 생성, 값이 10일 때는 &#39;100%&#39;가 생성
});

...
// Animated 연결
&lt;Animated.View style={{ width: widthAnimation }} /&gt;</code></pre>
<hr>
<h3 id="🪸-animatedtiming">🪸 Animated.timing</h3>
<p>Animated.timing을 이용해서 Animated Value의 값을 변경할 수 있다.</p>
<pre><code class="language-jsx">Animated.timing(refAnimation, {
  toValue: 0, // 애니메이션의 목표, refAnimation이 이 값으로 변화 (필수⭐️)
  useNativeDriver: false, // 네이티브 드라이버 사용 여부
  duration: 1000, // 애니메이션의 지속 시간을 밀리초 단위로 지정 - 기본값 500
  delay: 0, // 애니메이션의 시작을 지연시킬 시간
  isInteraction: true, // 애니메이션이 다른 상호작용과 상호작용하는지 여부 - 기본값 true
  easing: Easing.inOut(Easing.ease), // 애니메이션의 가속도 함수를 설정, Easing 모듈을 사용하여 설정함 - 기본값 Easing.inOut(Easing.ease)
}).start(() =&gt; {
  // 애니메이션이 완료되었을 때 실행할 작업
})</code></pre>
<hr>
<h3 id="🪸-animated를-활용한-코드-예시">🪸 Animated를 활용한 코드 예시</h3>
<p>7초동안 왼쪽에서 오른쪽으로 부드럽게 움직이는 애니메이션</p>
<pre><code class="language-jsx">import { View, Animated } from &#39;react-native&#39;;
import React, { useEffect, useRef } from &#39;react&#39;;

...
const refAnimation = useRef(new Animated.Value(-380)).current;

useEffect(() =&gt; {
  Animated.timing(refAnimation, {
    toValue: 0,
    delay: 0,
    duration: 7000,
    useNativeDriver: true,
  }).start(() =&gt; {
    navigation.goBack(); // 애니메이션이 끝나면 뒤로가기 됨
  });
}, [refAnimation, navigation]);

const animatedStyle = {
  height: &#39;100%&#39;,
  backgroundColor: &#39;skyblue&#39;,
  transform: [{translateX: refAnimation}],
};

...
&lt;Animated.View style={animatedStyle} /&gt;</code></pre>
<hr>
<h3 id="🪸-animated를-활용한-코드-예시2">🪸 Animated를 활용한 코드 예시2</h3>
<pre><code class="language-tsx">const TopImage = ({Img, isScrollable}: Props) =&gt; {
  const darkmode = isDarkmode();
  const notFoundImg = darkmode ? notfoundDarkImg : notfoundLightImg;
  const navigation = useNavigation&lt;NativeStackNavigationProp&lt;any&gt;&gt;();

  const heightAni = useRef(new Animated.Value(222)).current;
  const bgColorAni = useRef(new Animated.Value(0)).current;

  useEffect(() =&gt; {
    Animated.timing(heightAni, {
      toValue: isScrollable ? 44 : 222,
      duration: 300,
      useNativeDriver: false,
    }).start();
    Animated.timing(bgColorAni, {
      toValue: isScrollable ? 0 : 1,
      duration: 300,
      useNativeDriver: false,
    }).start();
  }, [isScrollable, heightAni, bgColorAni]);

  const backgroundColor = bgColorAni.interpolate({
    inputRange: [0, 1],
    outputRange: [&#39;rgba(255, 255, 255, 1)&#39;, &#39;rgba(255, 255, 255, 0)&#39;],
  });

  return (
    &lt;Animated.View
      style={{height: heightAni, backgroundColor}}
      className=&quot;relative&quot;&gt;
      &lt;Animated.View style={{opacity: bgColorAni}}&gt;
        &lt;ImageBackground
          source={Img ? {uri: Img} : notFoundImg}
          resizeMode=&quot;cover&quot;
          className=&quot;h-[222px] w-full&quot;
        /&gt;
      &lt;/Animated.View&gt;
      &lt;Pressable
        onPress={() =&gt; navigation.goBack()}
        className=&quot;absolute left-[16px] top-[6px]&quot;&gt;
        &lt;ImageBackground
          source={blurBg}
          resizeMode=&quot;cover&quot;
          className=&quot;flex justify-center items-center rounded-[8px] w-[32px] h-[32px]&quot;&gt;
          &lt;LeftArrowIcon /&gt;
        &lt;/ImageBackground&gt;
      &lt;/Pressable&gt;
    &lt;/Animated.View&gt;
  );
};</code></pre>
<p><br><br></p>
<p>👉 <a href="https://reactnative.dev/docs/animated">(공식문서)React-Native Animated</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RN] React-Native 디버깅]]></title>
            <link>https://velog.io/@tata-v_vlelog/RN-React-Native-%EB%94%94%EB%B2%84%EA%B9%85</link>
            <guid>https://velog.io/@tata-v_vlelog/RN-React-Native-%EB%94%94%EB%B2%84%EA%B9%85</guid>
            <pubDate>Fri, 04 Aug 2023 07:25:57 GMT</pubDate>
            <description><![CDATA[<p>🛠️ React Native의 디버깅 도구 🛠️</p>
<h3 id="1-react-native-debugger">1. react-native-debugger</h3>
<p>react-native-debugger 설치</p>
<pre><code class="language-bash">brew install --cask react-native-debugger</code></pre>
<p>런치패드에서 잘 설치된 것을 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/tata-v_vlelog/post/1b840ed7-fd26-488a-8dc0-7b7d40a038e4/image.png" alt=""></p>
<hr>
<h3 id="2-디버그-앱과-연결">2. 디버그 앱과 연결</h3>
<p>React Native Debugger 앱을 열어놓은 상태에서
시뮬레이터에서 <code>cmd</code> + <code>m</code>을 눌러 &#39;Debug&#39;를 클릭하면 디버그 앱과 연결된다.</p>
<p><img src="https://velog.velcdn.com/images/tata-v_vlelog/post/d6b153ef-af42-428d-ae84-7405c1b8ba5a/image.png" alt=""></p>
<p>(<code>console.log(&#39;🎶🎵🎶🎵🎶&#39;)</code>을 해주어서 잘 찍히나 확인해 봄)</p>
<p><img src="https://velog.velcdn.com/images/tata-v_vlelog/post/e9e6e30a-4d51-4b9a-acce-86d6d3a7e888/image.png" alt=""></p>
<hr>
<h3 id="3-style-확인">3. style 확인</h3>
<p>− 시뮬레이터에서 <code>cmd</code> + <code>m</code>을 눌러 &#39;Show Element Inspector&#39;를 클릭.
− 스타일을 확인하고 싶은 컴포넌트를 시뮬레이터 화면에서 클릭.
− 디버거 앱에서 &quot;Inspect&quot;를 선택하면 해당 컴포넌트의 스타일 정보를 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/tata-v_vlelog/post/1fd55bcd-5172-4f84-aad0-5923c70150ac/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/tata-v_vlelog/post/47a9351f-850f-42cd-b452-dbf43e5ebcd0/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RN] 📑CLI로 React Native 시작하기]]></title>
            <link>https://velog.io/@tata-v_vlelog/RN-CLI%EB%A1%9C-React-Native-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@tata-v_vlelog/RN-CLI%EB%A1%9C-React-Native-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 18 Jul 2023 02:06:53 GMT</pubDate>
            <description><![CDATA[<h2 id="▷-cli로-react-native-시작하기">▷ CLI로 React Native 시작하기</h2>
<pre><code class="language-bash"># 설치
brew install watchman
sudo gem install cocoapods
# pod 버전 업데이트하기
# sudo gem update cocoapods

# 프로젝트 생성
npx react-native@latest init 폴더이름</code></pre>
<hr>
<h3 id="📑-mac에서-ios-개발을-위한-세팅">📑 <a href="https://reactnative.dev/docs/environment-setup?guide=native&amp;platform=ios">Mac에서 IOS 개발을 위한 세팅</a></h3>
<p><strong>ruby 버전 오류가 뜬다면</strong></p>
<pre><code class="language-bash"># Ruby 버전 맞춰주기
# 1. rbenv 설치 
     brew install rbenv ruby-build
# 2. ruby 설치
     rbenv install 2.x.x
# 3. ruby 버전 번경
     rbenv global 2.x.x
# 4. ruby path 수정
     vi ~/.zshrc
#    i를 눌러 입력모드로 =&gt; 아래 코드를 맨 아래에 붙여넣기 =&gt; :wq로 저장 후 닫기
     eval &quot;$(rbenv init - zsh)&quot;</code></pre>
<p><strong>Metro 번들러 시작하기</strong></p>
<pre><code class="language-bash">yarn start
# 또는
# npx react-native start
# port 번호 다르게 시작
# npx react-native start --port=8082
# 캐시 리셋
# npx react-native start --reset-cache</code></pre>
<p><strong>pod 설치</strong></p>
<pre><code>cd ios
pod install</code></pre><p><strong>ios/.xcode.env 파일</strong></p>
<pre><code class="language-js">// ios/.xcode.env 파일에서
// export NODE_BINARY=$(command -v node) 를
// export NODE_BINARY=your_node_path 로 바꿔야 함
export NODE_BINARY=/Users/myuser/.nvm/versions/node/v20.12.2/bin/node</code></pre>
<p><strong>ios 실행</strong></p>
<pre><code class="language-bash">npm run ios
# 또는
# npx react-native run-ios
# 특정 기기로 실행
# npx react-native run-ios --simulator=\&quot;iPhone 15 Pro\&quot;
# 기기 확인
# xcrun simctl list devices</code></pre>
<hr>
<h3 id="📑-mac에서-android-개발을-위한-세팅">📑 <a href="https://reactnative.dev/docs/environment-setup?guide=native&amp;platform=android">Mac에서 android 개발을 위한 세팅</a></h3>
<pre><code class="language-bash"># 설치
brew tap homebrew/cask-versions
brew install --cask zulu11</code></pre>
<p><strong>⒈ **모바일 에뮬레이션 [</strong>Android Studio**](<a href="https://developer.android.com/studio">https://developer.android.com/studio</a>) 설치</p>
<p><a href="https://jiwon152.tistory.com/entry/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%97%90%EB%AE%AC%EB%A0%88%EC%9D%B4%ED%84%B0-%EC%9D%B4%EC%9A%A9%ED%95%B4%EC%84%9C-Macbook-m1-%EC%8B%A4%EB%A6%AC%EC%BD%98%EC%97%90-%EC%9B%90%EC%8A%A4%ED%86%A0%EC%96%B4-%EC%84%A4%EC%B9%98%ED%95%98%EA%B3%A0-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0">참고</a></p>
<pre><code class="language-bash"># 설치
brew install --cask android-studio</code></pre>
<p>(<code>Android SDK</code> <code>Android SDK Platform</code> <code>Android Virtual Device</code> 가 체크되어 있는지 확인 후 설치하기)</p>
<p><strong>⒉</strong> Android Studio의 SDK Manager에 들어가 공식문서를 보며 설정해주기</p>
<p><img src="https://velog.velcdn.com/images/tata-v_vlelog/post/56d18771-69f7-435a-9b69-57fd68390668/image.png" alt=""></p>
<p><strong>⒊</strong> Android Studio의 설정에서 경로 확인</p>
<p><img src="https://velog.velcdn.com/images/tata-v_vlelog/post/c0b494a0-173d-44b6-9053-62951bfd1bd5/image.png" alt=""></p>
<pre><code class="language-bash"># 1. 수정하러 가기
vi ~/.zprofile

# 2. i를 눌러 입력모드로 =&gt; 아래 코드를 붙여넣기 =&gt; :wq로 저장 후 닫기
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/platform-tools

# 환경 설정 파일이 로드
source $HOME/.zprofile

# 경로 확인하기
echo $PATH</code></pre>
<p><strong>⒋ Metro 번들러 시작하기</strong></p>
<pre><code class="language-bash">npm start
# 또는
# npx react-native start</code></pre>
<p><strong>⒌ android 실행</strong></p>
<pre><code class="language-bash">npm run android
# 또는
# npx react-native run-android

# 디바이스들 확인
# emulator -list-avds
# 특정 디바이스 실행
# emulator -avd Pixel_8_Pro_API_33 &amp; npx react-native run-android</code></pre>
<hr>
<h3 id="🗑️--캐시-제거">🗑️  캐시 제거</h3>
<pre><code class="language-bash"># ios 캐시 제거
cd ios
rm -rf Podfile.lock Pods
pod install
yarn ios

# android 캐시 제거
cd android
./gradlew clean
./gradlew build


# ios
yarn cache clean
rm -rf ios/build ios/Pods node_modules
yarn install
cd ios &amp;&amp; pod install &amp;&amp; cd ..
yarn ios

# xcworkspace에서 빌드하기
Shift + Command + K # 빌드 캐시 초기화
Command + R # 다시 실행</code></pre>
<hr>
<h3 id="🫢-error---watchman">🫢 Error - watchman</h3>
<p>npm start로 실행했을 때
_<a href="https://facebook.github.io/watchman/docs/troubleshooting.html#/recrawl">&#39;watchman warging: Recrawled this...&#39;</a>_오류가 뜬다면</p>
<pre><code class="language-bash"># 차례대로 입력
watchman watch-del-all
watchman shutdown-server</code></pre>
<p>그럼에도 계속 오류가 뜬다면 재설치</p>
<pre><code class="language-bash">watchman shutdown-server
brew update
brew reinstall watchman
watchman</code></pre>
<hr>
<h3 id="🫢-error---adb">🫢 Error - adb</h3>
<p><code>a</code> 입력으로 android를 실행시킬 때
_&#39;...AdbCommandRejectedException: closed&#39;_오류가 뜬다면</p>
<pre><code class="language-bash"># 차례대로 입력
adb kill-server
adb start-server</code></pre>
<hr>
<h3 id="🫢-error---안드로이드-빌드-오류-kotlin">🫢 Error - 안드로이드 빌드 오류 kotlin</h3>
<p><em>&#39;Task :app:compileDebugJavaWithJavac FAILED w: Detected multiple Kotlin daemon sessions at build/kotlin/sessi&#39;</em></p>
<p>안드로이드 빌드 중 위와 같은 오류가 발생했다.</p>
<p>android/build.gradle파일을 열어서 kotlin에 대한 정보를 추가해주면 된다.</p>
<pre><code class="language-js">/* android/build.gradle */
buildscript {
    ext {
        buildToolsVersion = &quot;33.0.0&quot;
        minSdkVersion = 21
        compileSdkVersion = 33
        targetSdkVersion = 33
        kotlin_version = &#39;1.6.10&#39; // 추가⭐️

        ndkVersion = &quot;23.1.7779620&quot;
    }
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath(&quot;com.android.tools.build:gradle&quot;)
        classpath(&quot;com.facebook.react:react-native-gradle-plugin&quot;)
        classpath(&quot;org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version&quot;) // 추가⭐️
    }
}</code></pre>
<hr>
<h3 id="🫢-error---no-bundle-url-present">🫢 Error - No bundle URL present</h3>
<p><a href="https://velog.io/@haerim95/React-Native-Error-Log-No-bundle-URL-present-%EC%9D%B4%EC%8A%88">No bundle URL present</a></p>
<pre><code class="language-bash">yarn react-native bundle --entry-file=&#39;index.js&#39; --bundle-output=&#39;./ios/main.jsbundle&#39; --dev=false --platform=&#39;ios&#39;</code></pre>
<hr>
<h3 id="🫢-warn---customiseview">🫢 Warn - customiseView</h3>
<p><a href="https://github.com/reactwg/react-native-new-architecture/discussions/154">Introducing Bridgeless Mode</a></p>
<hr>
<h3 id="💥-에뮬레이터-강제-종료">💥 에뮬레이터 강제 종료</h3>
<pre><code class="language-bash">pkill -f &quot;qemu-system&quot;
pkill -f &quot;emulator&quot;
pkill -f &quot;adb&quot;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 🍒Recoil 사용법]]></title>
            <link>https://velog.io/@tata-v_vlelog/React-Recoil-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@tata-v_vlelog/React-Recoil-%EC%82%AC%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Fri, 16 Jun 2023 03:48:40 GMT</pubDate>
            <description><![CDATA[<h2 id="▷-recoil">▷ Recoil</h2>
<p>간편하고 유연한 상태 관리를 도와주는 Recoil은
atom과 selector, 두 가지 유형의 상태를 정의할 수 있다.</p>
<p><code>atom</code>: 변경 가능한 상태를 정의
<code>selector</code>: 다른 상태의 파생 값이나 변환된 값을 계산하기 위해 사용</p>
<h3 id="🍒-설치">🍒 설치</h3>
<pre><code class="language-bash">npm install recoil</code></pre>
<hr>
<h3 id="🍒-recoilroot">🍒 RecoilRoot</h3>
<p>index.js</p>
<pre><code class="language-js">// RecoilRoot 가져오기
import { RecoilRoot } from &quot;recoil&quot;;

const root = ReactDOM.createRoot(document.getElementById(&quot;root&quot;));
root.render(
  &lt;React.StrictMode&gt;
    &lt;RecoilRoot&gt;  // 추가
      &lt;App /&gt;
    &lt;/RecoilRoot&gt; // 추가
  &lt;/React.StrictMode&gt;
);</code></pre>
<hr>
<h3 id="🍒-atom">🍒 atom</h3>
<p>atom/usernameState.js</p>
<pre><code class="language-js">// atom 가져오기
import { atom } from &quot;recoil&quot;;

const usernameState = atom({
  key: &quot;usernameState&quot;,
  default: &quot;tata&quot;,
});

export default usernameState;</code></pre>
<hr>
<h3 id="🍒-selector">🍒 selector</h3>
<p>get 함수를 정의하여 해당 상태를 계산할 수 있다.</p>
<p>selector/nameLengthState.js</p>
<pre><code class="language-js">// selector 가져오기
import { selector } from &quot;recoil&quot;;

import usernameState from &quot;../atom/usernameState&quot;;

const nameLengthState = selector({
  key: &quot;nameLengthState&quot;,
  get: ({ get }) =&gt; {
    const username = get(usernameState);
    return username.length;
  },
});

export default nameLengthState;</code></pre>
<hr>
<h3 id="🍒-userecoilstate-userecoilvalue">🍒 useRecoilState, useRecoilValue</h3>
<p><code>useRecoilState</code>: useState처럼 상태 값 변경도 가능
<code>useRecoilValue</code>: 상태 값만 가져오기</p>
<pre><code class="language-js">import { useRecoilState, useRecoilValue } from &quot;recoil&quot;;
import nameState from &quot;../atom/usernameState&quot;;
import nameLengthState from &quot;../selector/nameLengthState&quot;;

const Main = () =&gt; {
  const [username, setUsername] = useRecoilState(nameState);

  const name = useRecoilValue(nameState);
  const nameLength = useRecoilValue(nameLengthState);

  const changeName = () =&gt; {
    setUsername(&quot;chimmy&quot;);
  };

  const resetName = () =&gt; {
    setUsername(&quot;tata&quot;);
  };

  return (
    &lt;&gt;
      &lt;span&gt;{username}&lt;/span&gt;
      &lt;button onClick={changeName}&gt;Change Name&lt;/button&gt;
      &lt;button onClick={resetName}&gt;Reset&lt;/button&gt;
      &lt;p&gt;{name}&lt;/p&gt;
      &lt;p&gt;{nameLength}&lt;/p&gt;
    &lt;/&gt;
  );
};

export default Main;</code></pre>
<p><img src="https://velog.velcdn.com/images/tata-v_vlelog/post/653ea892-57b8-4a13-95c9-fa5d8cb6f8ed/image.gif" alt=""></p>
<hr>
<h3 id="🍒-usesetrecoilstate-useresetrecoilstate">🍒 useSetRecoilState, useResetRecoilState</h3>
<p><code>useSetRecoilState</code>: 상태 값 변경 가능
<code>useResetRecoilState</code>: 디폴트 상태 값으로 변경</p>
<pre><code class="language-js">import { useSetRecoilState, useResetRecoilState, useRecoilValue } from &quot;recoil&quot;;
import nameState from &quot;../atom/usernameState&quot;;

const Main = () =&gt; {
  const username = useRecoilValue(nameState);

  const resetName = useResetRecoilState(nameState); // 상태 값 변경 가능
  const change = useSetRecoilState(nameState); // 디폴트 상태 값으로 변경

  const changeName = () =&gt; {
    change(&quot;chimmy&quot;);
  };

  return (
    &lt;&gt;
      &lt;span&gt;{username}&lt;/span&gt;
      &lt;button onClick={changeName}&gt;Change Name&lt;/button&gt;
      &lt;button onClick={resetName}&gt;Reset&lt;/button&gt;
    &lt;/&gt;
  );
};

export default Main;</code></pre>
<hr>
<p>useSetRecoilState로만 상태 값 변경할 때</p>
<pre><code class="language-jsx">/* atom/infoState.js */
import { atom } from &#39;recoil&#39;;

const defaultValue = {
  info2: &#39;&#39;,
  info4: &#39;&#39;,
  date: &#39;&#39;,
  info5: &#39;&#39;,
  info6: [],
};

const infoState = atom({
  key: &#39;infoState&#39;,
  default: defaultValue,
});

export default infoState;</code></pre>
<pre><code class="language-jsx">/* Title.jsx */
import { useSetRecoilState } from &#39;recoil&#39;;

function Title() {
  const setInfoState = useSetRecoilState(infoState);

  const handleInputChange = (e) =&gt; {
    const value = e.target.value;

    setInfoState((data) =&gt; ({ ...data, info2: value }));
  };
...</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TS] 🧤JS로 작성된 React 프로젝트를 TS로 바꾸는 방법]]></title>
            <link>https://velog.io/@tata-v_vlelog/TS-React%EC%97%90%EC%84%9C-typescript-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-qksazupr</link>
            <guid>https://velog.io/@tata-v_vlelog/TS-React%EC%97%90%EC%84%9C-typescript-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-qksazupr</guid>
            <pubDate>Thu, 01 Jun 2023 06:14:02 GMT</pubDate>
            <description><![CDATA[<h2 id="▷-typescript-환경-설정">▷ typescript 환경 설정</h2>
<p>CRA로 typescript 시작할 시</p>
<pre><code class="language-bash">npx create-react-app my-app --template typescript</code></pre>
<p>기존 javascript로 작성된 React 프로젝트에 typescript를 추가하게 될 경우</p>
<pre><code class="language-bash">npm install typescript @types/node @types/react @types/react-dom @types/jest @types/react-router-dom

# 리덕스 툴킷
npm install --save-dev @reduxjs/toolkit @types/react-redux

# 스타일드 컴포넌트
npm install @types/styled-components

# 스토리북
npm install --save-dev @storybook/preset-typescript

# tsconfig.json 생성
npx tsc --init

# npm install @types/react를 해줬는데도 안 떠서 다시 설치함
# npm install --save-dev @types/react</code></pre>
<hr>
<h3 id="🧤-tsconfigjson에-내용-추가">🧤 tsconfig.json에 내용 추가</h3>
<pre><code class="language-js">&quot;compilerOptions&quot;: {
  &quot;jsx&quot;: &quot;react&quot;,
  &quot;types&quot;: [&quot;styled-components&quot;],
}</code></pre>
<hr>
<h3 id="🧤-indextsx">🧤 index.tsx</h3>
<pre><code class="language-js">const root = ReactDOM.createRoot(document.getElementById(&quot;root&quot;) as HTMLElement);

root.render(
  &lt;React.StrictMode&gt;
    &lt;Provider store={store}&gt;
      &lt;App /&gt;
    &lt;/Provider&gt;
  &lt;/React.StrictMode&gt;
);</code></pre>
<hr>
<h3 id="🧤-svg-png-이미지-파일-인식-못하는-경우">🧤 svg, png... 이미지 파일 인식 못하는 경우</h3>
<p>src폴더 안에 custom.d.ts파일 생성</p>
<pre><code class="language-js">/* src/custom.d.ts */

declare module &quot;*.svg&quot; {
  const content: any;
  export default content;
}

declare module &quot;*.png&quot; {
  const content: any;
  export default content;
}</code></pre>
<p>tsconfig.json에 내용 추가</p>
<pre><code class="language-js">/* tsconfig.json */

&quot;include&quot;: [&quot;src&quot;, &quot;src/custom.d.ts&quot;],
&quot;exclude&quot;: [&quot;node_modules&quot;]</code></pre>
<hr>
<h2 id="▷-redux에서-typescript-사용법">▷ Redux에서 typescript 사용법</h2>
<h3 id="🧤-store">🧤 Store</h3>
<p>Store/index.ts</p>
<pre><code class="language-js">/* Store/index.ts */
import { legacy_createStore as createStore, combineReducers } from &quot;redux&quot;;
import { todoReducer } from &quot;../Reducers&quot;;

const rootReducers = combineReducers({ todoReducer });
export type RootState = ReturnType&lt;typeof rootReducers&gt;; // 여기 추가

const store = createStore(rootReducers);

export default store;</code></pre>
<hr>
<h3 id="🧤-actions">🧤 Actions</h3>
<p>Actions/action.tsx</p>
<p>⒈ 타입지정 및 as 지정</p>
<pre><code class="language-js">export const CREATE: string = &quot;create&quot; as const;</code></pre>
<p>⒉ enum으로 타입 지정</p>
<pre><code class="language-js">export enum ActionTypes {
  CREATE = &quot;create&quot;,
  REMOVE = &quot;remove&quot;,
  TOGGLE = &quot;toggle&quot;,
  DOING_FILTER = &quot;doingFilter&quot;,
  COMPLE_FILTER = &quot;compleFilter&quot;,
  CHANGE_TEXT = &quot;changeText&quot;,
}</code></pre>
<p>⒊ 액션 함수에 맞게 리턴 타입 지정</p>
<pre><code class="language-js">// 인터페이스
export interface ITodos {
  id: number;
  text: string;
  done: boolean;
}

interface ICreateAction {
  type: ActionTypes.CREATE;
  payload: ITodos;
}
.....

// 액션 함수
export const create = (todo: ITodos): ICreateAction =&gt; {
  return {
    type: ActionTypes.CREATE,
    payload: todo,
  };
};
.....

// 액션 타입들
export type TTodoAction = ICreateAction | IRemoveAction | IToggleAction | IDoingFilterAction | ICompleFilterAction | IChangeTextAction;</code></pre>
<hr>
<h3 id="🧤-reducers">🧤 Reducers</h3>
<p>Reducers/initialState.js</p>
<pre><code class="language-js">mport { ITodos } from &quot;../Actions/action&quot;;

export interface ITodosArr {
  todos: ITodos[];
  doing: ITodos[];
  completed: ITodos[];
}

export const initialState: ITodosArr = {
  todos: [
    {
      id: 1,
      text: &quot;간지나게 숨쉬기&quot;,
      done: true,
    },
 .....</code></pre>
<hr>
<p>Reducers/index.js</p>
<pre><code class="language-js">export const todoReducer = (state: ITodosArr = initialState, action: TTodoAction) =&gt; {
  switch (action.type) {
    case CREATE:
      const createPayload = action.payload as ITodos; // payload 타입을 분기 처리
      return Object.assign({}, state, { todos: [...state.todos, createPayload], doing: [...state.doing, createPayload] });

    case REMOVE:
      const removePayload = action.payload as { id: number }; // payload 타입을 분기 처리
      const removeTodos = state.todos.filter((el: ITodos) =&gt; el.id !== removePayload.id);
     .....

    case CHANGE_TEXT:
      const changeTextPayload = action.payload as { id: number; text: string }; // payload 타입을 분기 처리
      const changeTodos = state.todos.map((el: ITodos) =&gt; {
        if (el.id === changeTextPayload.id) return { ...el, text: changeTextPayload.text };
        return el;
      });
      .....</code></pre>
<hr>
<h2 id="▷-react-컴포넌트에서-typescript">▷ React 컴포넌트에서 typescript</h2>
<h3 id="🧤-children">🧤 children</h3>
<p><code>React.ReactNode</code></p>
<pre><code class="language-jsx">export interface ITemplateProps {
  children: React.ReactNode;
}

const TodoTemplateNotice = ({ children }: ITemplateProps) =&gt; {
  return (
    &lt;TodoTemplateBlock&gt;
      &lt;RealTemplateBlock&gt;{children}&lt;/RealTemplateBlock&gt;
    &lt;/TodoTemplateBlock&gt;
  );
};</code></pre>
<hr>
<h3 id="🧤-mouseevent">🧤 MouseEvent</h3>
<p><code>MouseEvent</code></p>
<pre><code class="language-jsx">useEffect(() =&gt; {
    const outInput = (e: MouseEvent) =&gt; {
      const node = e.target as Node;
      if (isEditable &amp;&amp; inputRef.current &amp;&amp; !inputRef.current.contains(node)
      .....</code></pre>
<hr>
<h3 id="🧤-etargetvalue">🧤 e.target.value</h3>
<p><code>React.ChangeEvent&lt;HTMLInputElement&gt;</code></p>
<pre><code class="language-jsx">  const onChange = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    setValue(e.target.value);
  };</code></pre>
<hr>
<h3 id="🧤-epreventdefault">🧤 e.preventDefault</h3>
<p><code>React.FormEvent</code></p>
<pre><code class="language-jsx">const onSubmit = (e: React.FormEvent) =&gt; {
  e.preventDefault();
}</code></pre>
<hr>
<h3 id="🧤-useref">🧤 useRef</h3>
<p><code>&lt;HTMLInputElement&gt;</code></p>
<pre><code class="language-jsx">const inputRef = useRef&lt;HTMLInputElement&gt;(null);</code></pre>
<hr>
<h3 id="🧤-setstate">🧤 setState</h3>
<p><code>React.Dispatch&lt;React.SetStateAction&lt;타입&gt;&gt;</code></p>
<p>```jsx
interface IAdcButtonProps {
  setBtnTxt: React.Dispatch&lt;React.SetStateAction<string>&gt;;
  setAll: React.Dispatch&lt;React.SetStateAction<boolean>&gt;;
  setFilterTxt: React.Dispatch&lt;React.SetStateAction<string>&gt;;
}</p>
<p>const AdcButton = ({ setBtnTxt, setAll, setFilterTxt }: IAdcButtonProps) =&gt; {</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 🎧react-range 사용법]]></title>
            <link>https://velog.io/@tata-v_vlelog/React-react-range-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@tata-v_vlelog/React-react-range-%EC%82%AC%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Sat, 20 May 2023 14:19:19 GMT</pubDate>
            <description><![CDATA[<h2 id="▷-react-range">▷ react-range</h2>
<p>input range를 대신해서 사용 할 수 있는 라이브러리이다.</p>
<p>(input range를 사용하면 왼쪽 부분을 칠해줄 수가 없었는데,
react-range를 사용해서 왼쪽 부분을 칠해줄 수 있게 되었다.)</p>
<h3 id="🎧-설치">🎧 설치</h3>
<pre><code class="language-bash">npm install react-range</code></pre>
<hr>
<h3 id="🎧-예시-코드">🎧 예시 코드</h3>
<pre><code class="language-jsx">import { Range } from &#39;react-range&#39;; // Range 가져오기
...
......
return (
  &lt;StyledRange // Range
    values={[volume]}
    step={1}
    min={0}
    max={100}
    onChange={handleVolumeChange}
    renderTrack={({ props, children }) =&gt; ( // renderTrack =&gt; 가로 바
      &lt;Track {...props} values={[volume]}&gt;
        {children}
      &lt;/Track&gt;
    )}
    // renderThumb =&gt; 음량 조절 동그라미
    renderThumb={({ props, isDragged }) =&gt; &lt;Thumb {...props} isDragged={isDragged} volume={[volume]} /&gt;}
  /&gt;
);</code></pre>
<hr>
<p>styled-components</p>
<pre><code class="language-jsx">// Range
const StyledRange = styled(Range)`
  height: 8px;
  display: flex;
  width: 400px;
`;

// 가로 바
const Track = styled.div`
  height: 8px;
  width: 400px;
  border-radius: 5px;
  background: linear-gradient(
    to right,
    #5d7a80,
    #2f7381 ${({ values }) =&gt; (values[0] / 100) * 100}%,
    #131c23 ${({ values }) =&gt; (values[0] / 100) * 100}%
  );
  align-self: center;
  box-shadow: 2px 2px 5px rgba(122, 153, 164, 0.2);
`;

// 음량 조절 동그라미
const Thumb = styled.div`
  height: 24px;
  width: 24px;
  border-radius: 50%;
  background: ${({ isDragged }) =&gt;
    isDragged ? &#39;radial-gradient(ellipse at top, #9abfcd, #4f98a6);&#39; : &#39;radial-gradient(ellipse at top, #7a99a4, #2f7381);&#39;};
  display: flex;
  align-items: center;
  box-shadow: 0 2px 5px rgba(19, 28, 35, 0.2);
  transform: translateX(-50%);
  left: ${({ value, min, max }) =&gt; `${((value - min) / (max - min)) * 100}%`};
  top: 0px;
  position: absolute;

  &amp;:focus {
    outline: none;
  }
`;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[GitHub] 📁리포지토리 언어 변경 ]]></title>
            <link>https://velog.io/@tata-v_vlelog/GitHub-%EB%A6%AC%ED%8F%AC%EC%A7%80%ED%86%A0%EB%A6%AC-%EC%96%B8%EC%96%B4-%EB%B3%80%EA%B2%BD</link>
            <guid>https://velog.io/@tata-v_vlelog/GitHub-%EB%A6%AC%ED%8F%AC%EC%A7%80%ED%86%A0%EB%A6%AC-%EC%96%B8%EC%96%B4-%EB%B3%80%EA%B2%BD</guid>
            <pubDate>Sun, 14 May 2023 09:20:20 GMT</pubDate>
            <description><![CDATA[<h2 id="▷-리포지토리-언어-변경">▷ 리포지토리 언어 변경</h2>
<p>GitHub에서 사용하는 언어 인식 도구인 Linguist는 파일의 내용을 분석하여 어떤 언어로 작성되었는지를 자동으로 인식한다.</p>
<p>(😯❓typescript로도 작성되어 있는데, 깃헙의 언어 비율에는 javascript만 있었다.)</p>
<hr>
<h3 id="📁-gitattributes-파일-생성">📁 .gitattributes 파일 생성</h3>
<p>일단 모든 언어를 무시하고, 인식할 언어를 설정한다.</p>
<pre><code class="language-js">/* .gitattributes */

* linguist-vendored
*.tsx linguist-vendored=false
*.js linguist-vendored=false</code></pre>
<p><img src="https://velog.velcdn.com/images/tata-v_vlelog/post/ecd4aa26-8288-4448-92e8-4194bc8c7783/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 🧾textarea 유동적인 높이 조절]]></title>
            <link>https://velog.io/@tata-v_vlelog/HTML-textarea-%EC%9C%A0%EB%8F%99%EC%A0%81%EC%9D%B8-%EB%86%92%EC%9D%B4-%EC%A1%B0%EC%A0%88</link>
            <guid>https://velog.io/@tata-v_vlelog/HTML-textarea-%EC%9C%A0%EB%8F%99%EC%A0%81%EC%9D%B8-%EB%86%92%EC%9D%B4-%EC%A1%B0%EC%A0%88</guid>
            <pubDate>Wed, 03 May 2023 06:44:21 GMT</pubDate>
            <description><![CDATA[<h3 id="🧾-textarea-유동적인-높이-조절">🧾 textarea 유동적인 높이 조절</h3>
<pre><code class="language-jsx">const PostContent = () =&gt; {
  const autoResizeTextarea = () =&gt; {
    let textarea = document.querySelector(&#39;.autoTextarea&#39;);

    if (textarea) {
      textarea.style.height = &#39;auto&#39;;
      let height = textarea.scrollHeight;
      let maxHeight = window.innerHeight * 0.74; // 뷰포트 높이의 74%로 최대 높이 지정
      textarea.style.height = `${Math.min(height + 8, maxHeight)}px`; // 높이가 최대 높이를 초과하지 않도록 함
    }
  };

  return (
    &lt;&gt;
      &lt;PostContentBlock&gt;
          &lt;PostContent&gt;
            &lt;div className=&#39;top-txt-box&#39;&gt;
              &lt;span className=&#39;post-txt&#39;&gt;포스트 쓰기&lt;/span&gt;
              &lt;span className=&#39;artist-txt&#39;&gt;BTS&lt;/span&gt;
            &lt;/div&gt;
            &lt;form className=&#39;post-form&#39;&gt;
              &lt;textarea
                className=&#39;autoTextarea&#39;
                onKeyDown={autoResizeTextarea}
                onKeyUp={autoResizeTextarea}
                type=&#39;text&#39;
                placeholder=&#39;내용을 입력해 주세요&#39;
                name=&#39;content&#39;
                autoComplete=&#39;off&#39;
                required
              /&gt;
            &lt;/form&gt;
            &lt;div className=&#39;submit-box&#39;&gt;
              &lt;button&gt;등록&lt;/button&gt;
            &lt;/div&gt;
          &lt;/PostContent&gt;
          &lt;div className=&#39;delete-btn&#39;&gt;
            &lt;img src={deleteBtn} alt=&#39;delete-button&#39; /&gt;
          &lt;/div&gt;
       &lt;/PostContentBlock&gt;
    &lt;/&gt;
  );
};</code></pre>
]]></description>
        </item>
    </channel>
</rss>