mkdir ./[폴더이름]
cd ./[폴더이름]
npx create-expo-app@latest --template expo-template-blank-typescript .
npm 으로 시작이 되었다면,
node_module package-lock.json 을 삭제하고 다음을 실행한다.
yarn install
nativewind
이 링크는 nativewind 깃허브 read.me 에서 소개한 nativewind 홈페이지 인데..
공식을 따라하다가 안되어서 구글링해서 방법을 찾았다.
yarn add nativewind@2.0.11 react-native-reanimated@3.10.1 tailwindcss@3.3.2
nativewind 는 2.0.11 버전,
react-native-reanimated 는 3.10.1 버전,
tailwindcss 는 3.3.2 버전으로 받아야한다.
그 이상의 버전을 사용하면 에러가 난다.
npx tailwindcss init
//tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./App.{js,jsx,ts,tsx}", "./src/**/*.{js,jsx,ts,tsx}"],
theme: {
extend: {},
},
plugins: [],
};
//babel.config.js
module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: ["nativewind/babel"],
};
};
nativewind-env.d.ts 파일을 생성해서 다음과 같이 써주면 해결된다.
//nativewind-env.d.ts
/// <reference types="nativewind/types" />
npx storybook@latest init
//metro.config.js
const path = require('path');
const { getDefaultConfig } = require('expo/metro-config');
const withStorybook = require('@storybook/react-native/metro/withStorybook');
const config = getDefaultConfig(__dirname);
module.exports = withStorybook(config, {
enabled: true,
configPath: path.resolve(__dirname, './.storybook'),
});
app.json 을 app.config.js로 바꾼뒤 다음과 같이 작성한다.
app.config.js를 사용하면, js를 사용해서 동적으로 설정을 해줄 수 있다.
//app.config.js
export default ({ config }) => ({
...config,
name: "등대",
slug: "lighthouse",
version: "1.0.0",
orientation: "portrait",
icon: "./assets/icon.png",
extra: {
storybookEnabled: process.env.STORYBOOK_ENABLED,
},
splash: {
image: "./assets/splash.png",
resizeMode: "contain",
backgroundColor: "#ffffff",
},
updates: {
fallbackToCacheTimeout: 0,
},
assetBundlePatterns: ["**/*"],
ios: {
supportsTablet: true,
},
android: {
adaptiveIcon: {
foregroundImage: "./assets/adaptive-icon.png",
backgroundColor: "#FFFFFF",
},
},
web: {
favicon: "./assets/favicon.png",
},
});
extra 설정을 통해서 expo 상수를 사용하여 앱에서 storybookEnabled 변수에 액세스할 수 있게 했다.
extra: {
storybookEnabled: process.env.STORYBOOK_ENABLED,
},
이런 식으로 사용한다는 말이다.
import Constants from 'expo-constants';
const apiUrl = Constants.expoConfig.extra.storybookEnabled;
이제 storybookEnabled 변수가 true 이면 스토리북이 실행되고, false 이면 앱이 실행되도록 바꾸자
...
import Constants from "expo-constants";
function App() { return ... }
let AppEntryPoint = App;
if (Constants.expoConfig?.extra?.storybookEnabled === "true") {
AppEntryPoint = require("./.storybook").default;
}
export default AppEntryPoint;
yarn add cross-env
STORYBOOK_ENABLED 플래그를 true로 설정하여 앱을 실행하는 새로운 명령을 추가,
yarn start를 실행하면 앱 코드가 표시되고,
yarn storybook를 실행하면 스토리북이 표시됩니다.
"scripts": {
"storybook": "cross-env STORYBOOK_ENABLED='true' expo start",
}
stories 폴더를 src밑에서 관리하기 위해 경로를 수정한다.
.storybook/main.ts
...
stories: ['../src/stories/**/*.stories.?(ts|tsx|js|jsx)'],
...
preview.tsx 파일을 수정해주어 스토리북에서도 폰트가 적용되게 한다.
//.storybook/preview.tsx
import { loadAsync } from 'expo-font';
const loadFonts = async () => {
await loadAsync({
"MusticaPro-SemiBold": require("../assets/fonts/MusticaPro-SemiBold.otf"),
"Pretendard-Bold": require("../assets/fonts/Pretendard-Bold.otf"),
"Pretendard-Regular": require("../assets/fonts/Pretendard-Regular.otf"),
});
};
(async () => {
await loadFonts();
// 폰트가 로드될 때까지 기다려~
})();
const preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
};
export default preview;
yarn add @react-navigation/native @react-navigation/native-stack
npx expo install react-native-screens react-native-safe-area-context
import {NavigationContainer} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import HomeScreen from "./src/screens/HomeScreen";
...
const Stack = createNativeStackNavigator();
...
function App() {
...
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{title: 'Welcome'}}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
yarn add --dev react-native-svg-transformer
스토리북 설정 위에 추가 작성 한다.
const { getDefaultConfig } = require("@expo/metro-config");
const withStorybook = require('@storybook/react-native/metro/withStorybook');
const path = require('path');
module.exports = ( () => {
const config = getDefaultConfig(__dirname);
const { transformer, resolver } = config;
config.transformer = {
...transformer,
babelTransformerPath: require.resolve("react-native-svg-transformer/expo")
};
config.resolver = {
...resolver,
assetExts: resolver.assetExts.filter((ext) => ext !== "svg"),
sourceExts: [...resolver.sourceExts, "svg"]
};
config.resolver.assetExts.push("otf"); // 'otf' 확장자 추가
return withStorybook(config, { // 두 개의 module.exports를 합침
enabled: true,
configPath: path.resolve(__dirname, './.storybook'),
});
})();
declare module '*.svg' {
const content: any;
export default content;
}
yarn add expo-font
/assets/fonts/ 에 원하는 폰트파일 .otf 를 옮긴다.
import * as Font from "expo-font";
...다른 코드 모두 생략
const fetchFonts = () => {
return Font.loadAsync({
"Pretendard-Medium": require("./assets/fonts/Pretendard-Medium.otf"),
});
};
...
export default function App() {
const [fontsLoaded, setFontsLoaded] = useState(false);
useEffect(() => {
const loadFonts = async () => {
await fetchFonts();
setFontsLoaded(true);
};
loadFonts();
}, []);
if (!fontsLoaded) {
// 폰트가 로드되기 전에는 아무것도 렌더링하지 않음
return null;
}
return (...
사용하고자 하는 파일에서 다음과 같은 형식으로 사용가능
<Text style={{fontFamily: "Pretendard-Medium"}}>text</Text>