CRA 생성
$ npx create-react-app frontend --template=typescript
폴더정리(안쓰는 파일들 삭제)
npm i -D eslint prettier eslint-config-prettier eslint-plugin-prettier @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest
backend에 있는 .eslintrc 파일과 prettierrc 파일 frontend에 붙여넣기
css framework
사용법
npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
npm install @craco/craco
package.json
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "craco eject"
}
npx tailwindcss-cli@latest init
craco.config.js
module.exports = {
purge: [],
purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
}
만약에 tailwind.config.js나 craco.config.js에서 module에 빨간줄이 뜨면 .eslintignore 추가
eslintignore
tailwind.config.js
craco.config.js
npm i react-router-dom
npm i -D @types/react-router-dom
App.tsx
import React, { FC } from 'react';
import Main from './pages/main';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const App: FC = () => {
return (
<Router>
<Switch>
<Route exact path="/" component={Main} />
</Switch>
</Router>
);
};
export default App;
pages/main.tsx
import React, { FC } from 'react';
const Main: FC = () => {
return (
<div className="flex bg-red-100 min-h-screen">
<div className="bg-blue-200 flex-auto">1</div>
<div className="bg-purple-200 max-w-screen-sm flex-auto">2</div>
<div className="bg-green-200 flex-auto">3</div>
</div>
);
};
export default Main;
components/Layout.tsx
import React, { FC } from 'react';
const Layout: FC = ({ children }) => {
return (
<div className="flex bg-red-100 min-h-screen">
<div className="bg-blue-200 flex-auto">1</div>
<div className="bg-purple-200 max-w-screen-sm flex-auto">{children}</div>
<div className="bg-green-200 flex-auto">3</div>
</div>
);
};
export default Layout;
App.tsx
import React, { FC } from 'react';
import Main from './pages/main';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Layout from './components/Layout';
const App: FC = () => {
return (
<Router>
<Layout>
<Switch>
<Route exact path="/" component={Main} />
</Switch>
</Layout>
</Router>
);
};
export default App;
pages/main.tsx
import React, { FC } from 'react';
const Main: FC = () => {
return <div>main</div>;
};
export default Main;
npm i axios swr
main.tsx
import React, { FC, useEffect } from 'react';
import axios from 'axios';
import useSWR from 'swr';
const Main: FC = () => {
const fetcher = async (url: string) => {
try {
const response = await axios.get(url);
return response.data;
} catch (error) {
console.error(error);
}
};
const { data, error } = useSWR('http://localhost:3010/tweets', fetcher);
useEffect(() => {
console.log(data);
}, [data]);
return <div>main</div>;
};
export default Main;
CORS error -> backend에서 접근 허용 설정
backend/main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
cors: { origin: process.env.FRONT_URL, credentials: true },
});
app.useGlobalPipes(new ValidationPipe());
await app.listen(process.env.PORT);
}
bootstrap();
.env
FRONT_URL=http://localhost:3000
backend 재시작(.env파일 수정시 재시작해야 반영)
pages/main.tsx
import React, { FC, useEffect } from 'react';
import axios from 'axios';
import useSWR from 'swr';
const Main: FC = () => {
const fetcher = async (url: string) => {
try {
const response = await axios.get(url);
return response.data;
} catch (error) {
console.error(error);
}
};
const { data, error } = useSWR('http://localhost:3010/tweets', fetcher);
useEffect(() => {
console.log(data);
}, [data]);
if (!data) return <div>loading...</div>;
if (error) return <div>error</div>;
return <div>main</div>;
};
export default Main;
.env
// CRA는 REACT_APP으로 시작해야한다
REACT_APP_BACK_URL=http://localhost:3010
src/components/common/Cards.tsx
import React, { FC } from 'react';
import { ITweet } from '../../interfaces';
import Card from './Card';
interface CardsProps {
tweets: ITweet[];
}
const Cards: FC<CardsProps> = ({ tweets }) => {
return (
<div>
{tweets.map((tweet) => {
return <Card key={tweet.id} />;
})}
</div>
);
};
export default Cards;
src/components/common/Card.tsx
import React, { FC } from 'react';
const Card: FC = () => {
return <div>Card</div>;
};
export default Card;
src/interfaces/index.ts
export interface ITweet {
id: number;
createdAt: Date;
tweet: string;
users: {
id: number;
nickname: string;
};
}
src/pages/main.tsx
import React, { FC, useEffect } from 'react';
import axios from 'axios';
import useSWR from 'swr';
import Cards from '../components/common/Cards';
import { ITweet } from '../interfaces';
const Main: FC = () => {
const fetcher = async (url: string) => {
try {
const response = await axios.get(url);
return response.data;
} catch (error) {
console.error(error);
}
};
const { data, error } = useSWR<ITweet[]>(
`${process.env.REACT_APP_BACK_URL}/tweets`,
fetcher,
);
if (!data) return <div className="bg-blue-700">Loading....</div>;
if (error) return <div>error</div>;
return <Cards tweets={data} />;
};
export default Main;
fontawesome 설치
npm i --save @fortawesome/fontawesome-svg-core
npm install --save @fortawesome/free-solid-svg-icons
npm install --save @fortawesome/react-fontawesome
npm install --save @fortawesome/free-brands-svg-icons
npm install --save @fortawesome/free-regular-svg-icons
components/common/ProfileIcon.tsx
import React, { FC } from 'react';
import { faUser } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
const ProfileIcon: FC = () => {
return (
<div className="rounded-full h-12 w-12 flex items-center justify-center bg-gray-300 hover:bg-gray-400">
<FontAwesomeIcon className="text-3xl text-blue-400" icon={faUser} />
</div>
);
};
export default ProfileIcon;
Cards.tsx
import React, { FC } from 'react';
import { ITweet } from '../../interfaces';
import Card from './Card';
interface CardsProps {
tweets: ITweet[];
}
const Cards: FC<CardsProps> = ({ tweets }) => {
return (
<div>
{tweets.map((tweet) => {
return <Card key={tweet.id} tweet={tweet} />;
})}
</div>
);
};
export default Cards;
Card.tsx
import React, { FC } from 'react';
import { ITweet } from '../../interfaces';
import ProfileIcon from './ProfileIcon';
interface CardProps {
tweet: ITweet;
}
const Card: FC<CardProps> = ({ tweet }) => {
return (
<div>
<ProfileIcon />
{tweet.tweet}
</div>
);
};
export default Card;