๋ง์ง๋ง ํ๋ก์ ํธ๋ฅผ ์์ํ๋ค!
๋ฒ์จ๋ถํฐ ๋์ด ๋๋ฌด ์์ฝ์ง๋ง ๊ทธ๋๋ ์ด๋ฒ ํ๋ก์ ํธ๋ ๋๋๋ ์ด์ฌํ ํด๋ณด๊ณ ์ ํ๋ค.
Issue Tracker Issue Cracker ๐ช ์์!
์ด๋ฒ ํ๋ก์ ํธ๋ Raccon , pyro, ๋ ธ์, Neo์ ํจ๊ปํ๋ 5์ธ ๋๊ฐ์กฑ!!!!!! ( ๋ค๊ฐ์ด ๋ฐฅ๋ ๋ชป๋จน์!@ )
์ฐ๋ฆฌ์ ๋ชฉํ๋ ์ด๊ฒ!
1) ํ์ ์คํฌ๋ฆฝํธ์ ์นํด์ง๊ธฐ
2) Recoil ์ด๋๋ ์นํด์ง๊ธฐ
3) ์๋ฌ์ฒ๋ฆฌ์ ์ต์ ํ๊น์ง
4) CSS ๊ฐ๋ฐ์์ ๋ฒ์ด๋์!!!! ( ๊ณผ์ฐ ๋ ๊น )
๋จ์ํ ๊นํ๋ธ ์ด์ ํฌ๋์ปค ์์ฑ์ ๋์ด์ ๋ฆฌ์ฝ์ผ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฐํ๊ฒ ์ดํดํ๊ณ ์ง์ ๊ตฌํํด๋ณด๋ ์๊ฐ์ ๊ฐ์ง ์์ ์ด๋ค.
๊ทธ๋ฌ๊ธฐ ์ํด์๋ ์ฒซ์ฃผ๋ถํฐ ๋ฌ๋ ค์ผ ํ๋๋ฐ ๋ฉด์ ์ด ํ๋ ์์ด์ ๋ง์์ด ๊ธํ๊ธด ํ์ง๋ง ์ด์ฌํ ํด๋ด์ผ์ง!
๊ฒ์๊ธ์ ์ฐ๋ ์ค์ ํ์ ์๋ฌ์ธ ๊ฒฝ์ฐ๊ฐ ๋ง์ ํ์ ์๋ฌ์ธ ๊ฒฝ์ฐ์ #type ํ๊ทธ๋ฅผ ๋ถ์ฌ๋๊ฒ ์ต๋๋ค ๐
๐ Typescirpt ๋ก OAuth ๊ตฌํ!
typescirpt ๋ก OAuth ๊ตฌํํด๋ณด๋ฉฐ ๊ฒช์ ๋๊ด๋ค
1) Callback.tsx ์ ํ์ #type
import { RouteComponentProps } from 'react-router-dom';
const Callback = ({ history, location }: RouteComponentProps): JSX.Element => {
const authUri = `http://`
const { code } = qs.parse(location.search, {
ignoreQueryPrefix: true,
});
//์ค๋ต
history.push('/main/issue-list');
//์ค๋ต
ํ์ ์คํฌ๋ฆฝํธ๊ฐ ์ฒ์์ด๋ค ๋ณด๋ React ์ Component๋ก return ํ ๊ฒ์ด ์ ํํ ์ด๋ค ํ์ ์ธ์ง ํค๋งธ๋๋ฐ,
JSX.Element์๋ค .
๊ทธ๋ฆฌ๊ณ Callback ์์์ ์ฌ์ฉํ๋ history์ location ์ ํ์ ์ RouteComponentProps๋ค
์ค์ RoutComponentProps ๋ด๋ถ๋ฅผ ๋ฏ์ด๋ณด๋ฉด ์ด๋ ๊ฒ ์๊ฒผ๋ค.
export interface RouteComponentProps<
Params extends { [K in keyof Params]?: string } = {},
C extends StaticContext = StaticContext,
S = H.LocationState
> {
history: H.History<S>;
location: H.Location<S>;
match: match<Params>;
staticContext?: C;
}
โ small Tip~
์ฒ์ ์ฝ๋ ๊ตฌํ ์์๋ ํ์ ์ด๋ค๊ฑธ ์ง์ ํ๋์ง ๋ชจ๋ฅด๊ฒ ์ด์ ์์ฒญ ํด๋งธ๋๋ฐ ,
mac๊ธฐ์ค ( command + ํด๋ฆญ) ํ๋ฉด ํด๋น ํ์ ์ ๋ด๋ถ๋ฅผ ๋ฏ์ด๋ณผ ์ ์๋ค! ๋งค์ฐ ์ข์ (๋ผ์ฟค์ด ์์ฐ์ค์ ์๋ ค์ฃผ์ฌ)
๐ Typescirpt๋ก Material UI ์ฌ์ฉ๊ธฐ
โ Icon Tabs ์ปค์คํ ์คํ์ผ ์ ํ๊ธฐ
์๋ ์ด๋ ๊ฒ ์์ด์ฝ๊ณผ ๋ผ๋ฒจ์ด coloumnํํ๋ก ๋์ด ์๋๋ฐ, ๋ด๊ฐ ํ๊ณ ์ถ์๊ฑด ์๋์ ๊ฐ์ row ์ ๋ ฌ ๊ณผ ํฌ๊ธฐ ์กฐ์
1) row ์ ๋ ฌ
์๋ณธ
<Tab icon={<FlagIcon />} label="๋ง์ผ์คํค" />
์์
<Tab
className={classes.tabWrapper}
label={
<div>
<FlagIcon style={{ verticalAlign: 'middle' }} /> ๋ง์ผ์คํค
</div>
}
/>
์ด๋ ๊ฒ ๋ฐ๊ฟ์ค์ผ๋ก์จ ๊ฒจ์ฐ ์ฑ๊ณต!!!!!!!
์ฐธ๊ณ : https://github.com/mui-org/material-ui/issues/11653
2) click Handler #type
์ฒ์ meterial UI์ Tab์ ์ฌ์ฉํ๋ ์ค์ ์ด๋ฒคํธ ํ์ ์ง์ ์์ ๊ณ์ ์ค๋ฅ๊ฐ ๋ฌ๋ค.
ํ์ ์คํฌ๋ฆฝํธ ๋จํ๋์ ์ฅ์ (??)์ธ ์ ์๋ฉ์ธ์ง๋ฅผ ๋ณด๋ Record<string, never >๋ฅผ ๋ฃ์ด๋ณด๋ผํด์ ๋ฃ์๋๋ ์ด๋จ๊ฒฐ์ ์ฑ๊ณต.?
export default function TabList(): JSX.Element {
const classes = useStyles();
const [value, setValue] = React.useState(0);
const handleChange = (
event: React.ChangeEvent<Record<string, unknown>>,
newValue: number
) => {
setValue(newValue);
};
return (
<Tabs
value={value}
onChange={handleChange}
* never ๊ณผ unknown ์ ์ฐจ์ด๋ -> https://simsimjae.tistory.com/464
์ฐธ๊ณ :https://github.com/mui-org/material-ui/issues/17454
3) style custom
Material UI๋ฅผ ์ฌ์ฉํ๋ฉด์ ๊ฐ์ฅ ๋ถํธํ ์ ์ style-components๋ก customํ๊ฒ ์คํ์ผ์ง์ ํ๊ธฐ๊ฐ ์ด๋ ต๋ค๋ ์ , ๊ทธ๋ก ์ธํด ๋ฐฉ๋ฒ์ ์ฐพ๋ ์ค useStyles๋ก์คํ์ผ์ ์ง์ ํ๋ค๋ ์ ์ ์์๋๋ค.
โ export ์๋ฌ
cannot be compiled under '--isolatedModules' because it is considered a global script file. Add an import, export, or an empty 'export {}' statement to make it a module. TS1208
ํด๊ฒฐ๊ณผ์ -> https://rrecoder.tistory.com/151
๐ Material UI ์์ styled-componets ์ฌ์ฉํ๊ธฐ
๊ฐ์ ์กฐ์ธ ์ด์๋จผ์ค , ์ดํฐ๋จผ์ค๊ป์ ํ๊ตฌํด์ค ๋์ ์ข์ ๋ด์ฉ์ ์๊ฒ ๋์๋ค!
๊ธฐ๋ณธ์ ์ผ๋ก๋ material UI์์ ์ค์ ํ ์คํ์ผ์ด styled-components์์ ์ง์ ํ ์คํ์ผ์ ๋จน๋๋ค
styled componets๊ฐ ์ ์ธ๋ ํ์ -> materialUI์์ ์ธ๋ผ์ธ์ผ๋ก ์ ์ธ๋๊ธฐ ๋๋ฌธ
ํด๊ฒฐ๋ฐฉ๋ฒ injectFirst
<StylesProvider injectFirst> ๋ฅผ ํตํด์ Material-UI → Styled-Components ์ ์์๋ก ๋ฐ๊ฟ ์ ์๋ค.
ํ๋จ ๋ธ๋ก๊ทธ์์ injectFirst ์ฌ์ฉ๋ฒ์ ํ์ธํ ์ ์๋ค .
๊ทธ๋ ๋ค๋ฉด materialUI์ useStyle์ ๊ตณ์ด ์ฌ์ฉํ๋ ์ด์ ๋?
: useStyle ๋ฅผ ์ฌ์ฉํ๋ฉด materialUI์ ํ์ ์ ์์์ ์ถ๋ก ํด์ฃผ๋ ์ฅ์ ์ด ์๋ค.
๐ IOS, FE, BE ํ์
โ ๊ฒ์๊ธ ์ ์ฅ ์ ์ด๋ฏธ์ง ์ ์ฅํ๋ ๋ฐฉ๋ฒ์ ๋ํ ๊ณ ๋ฏผ
1) ๊ฒ์๊ธ์ ์ ์ฅํ ๋ ์ด๋ฏธ์ง๋ฅผ ์ ์ฅ
์ฅ์ : ์ ์ฅ์ ๋ญ๋น๊ฐ ์์
๋จ์ : ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ํ์คํ ํด์ผ ํจ
2) ๊ฒ์๊ธ ์์ฑ ์ค ์ด๋ฏธ์ง๋ฅผ ์ ํํ ๋ ์ด๋ฏธ์ง ์ ์ฅ
์ฅ์ : ๊ตฌํ์ด ๋ ์ฝ๋ค
๋จ์ : ์ ์ฅ์ ๋ญ๋น
๐ ์๊ฐ๋ณด๋ค ์น๊ณผ ๋ชจ๋ฐ์ผ์์ ์ ํธํ๋ ์ฌ์ฉ์ UI ๊ฐ ๋ค๋ฅด๋ค๋ ๊ฑธ ๊นจ๋ฌ์๋ค.
IOS : ๋ง์ ๋ฒํผ๋ค์ด ์๋ ๊ฒ ๋ณด๋ค ํ๊ฐ์ง ๋ฒํผ์ผ๋ก ๊น๋ํ๊ฒ ์ฒ๋ฆฌํ๋ ๊ฒ์ด ์ข๋ค
web: ๊ตฌ์ฑ์ ์ ํ๋ฉด ๋ฒํผ์ด ๋์ด๋๋ ๊ฒ์ ํฌ๊ฒ ์๊ด ์๋ค ๋ผ๋ ๊ฒฐ๋ก ์ด ๋์ ์ผ๋จ ๊ฐ๋ฐ์ ์งํํ๋ฉฐ ์ถํ์ ์ถ๊ฐํ ๊ฒ์ ์ถ๊ฐํ๊ธฐ๋ก ํ๋ค.
โ ๋ฐฑ์๋ API ๋ก์ปฌ
์ด์ ๊น์ง๋ api ํ ์คํธ๋ฅผ ํด๋ณด๋ ค๋ฉด BE๋จ์์ ๋ฐฐํฌ๋ฅผ ํด์ผ์ง api์์ฒญ ๊ณผ ์๋ต์ด ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์
๋ฐฐํฌ ์ ๊น์ง๋ FE๋จ์์ ์์์ mock๋ฐ์ดํฐ๋ฅผ ๋ง๋ค์ด ์ฌ์ฉํํ ๋ฐ๋ชจ ์ ์ ํด๋น ๊ฐ๋ค์ ๋ณ๊ฒฝํด์ผ ํ๋ค.
์ด ๊ณผ์ ์์ FE๋จ์ด ๋ง๋ mock๊ณผ BE์ API ๋ณ์๋ช ๊ฐ์๊ฒ ์ ํํ ํ์๋์ง ์์ผ๋ฉด ์ค๋ฅ๊ฐ ๋ฐ์ํด ๋ฒ๊ฑฐ๋ก์์ด ์์๋๋ฐ
BE๋จ์ด ๊ตฌํ ์งํ์ค์ด ๋ฐฑ์๋ ์๋ฒ๋ฅผ ๋ด ์ปดํจํฐ์์ ๋์ปค๋ก ์คํํ ์ ์๊ฒ ์ค์ ํด๋์ผ์ ์ ๋ด๊ฐ ์ํ ๋๋ง๋ค ๋์ปค๋ก ์ด์ด์ ์ฌ์ฉํ๋ค!
์ฆ , ๋ด pc์์ ์๋ฒ๋ฅผ ๋์ปค๋ก ๋๋ฆฌ๊ณ , ๊ทธ ์์ ์๋ db์ ์์ฒญ์ ๋ณด๋ด๊ณ ์๋ต์ ๋ฐ์ ์ ์/๋ค.
๋ฐฑ์๋ ์์ด๋ API ์์ฒญ์ ํด๋ณผ ์ ์๋ค. ํ์ด๋ก ์ ๋ ธ์ ๊ฐ์ฌํด์ ใ ใ ใ
โ Component๊ฐ ๊ด๊ณ๋ ํ์
์ค๋์ ๊ฐ component๋ค์ ๊ด๊ณ์ ๋ํ ํ์๋ฅผ ํ๋ค.
์ญ์๋ BE, IOS, FE ์ ๊ด์ ์ ์ฐจ์ด๊ฐ ์์์ ์ฌ๋ฐ์๋ค.
BE: ์ผ๋จ BE๋ ์ ํ๋ํ๋์ ์์๋ฅผ entity๋ก ์นญํ๋ค. ๊ฐ ์ํฐํฐ์ 1:N ๊ด๊ณ๋ฅผ ๋ฏธ๋ฆฌ ์์ธํ ์ดํดํ๊ณ ์ฑ๋ฆฝํ๋ค.
FE : View ์์ฃผ๋ก component๋ฅผ ๋๋๋ค. ์ฌ์ฉ์ ์๋๋ฆฌ์ค์ ๋ง์ถฐ ์ฐจ๊ทผ์ฐจ๊ทผ ๊ด๊ณ๋ฅผ ์ดํดํ๊ณ ์ฑ๋ฆฝํ๋ค.
IOS: FE์ ๋น์ทํ๊ธด ํ๋ฐ , ์ฉ์ด๋ ๊ทธ๋ฐ๊ฒ๋ค์ด ์กฐ๊ธ์ฉ ๋ค๋ฅด๋ค .
๐ 1์ฐจ PR ํ ์ฝ๋๋ฆฌ๋ทฐ
1์ฐจ์ฝ๋๋ฆฌ๋ทฐ๋ฅผ ๋ค์ ๋ณด๋ ๊ทธ๋น์ ๊ณ ๋ฏผ์ด ์๋ก์๋ก ๋ ์ค๋ฅธ๋ค.
๊ทธ ๋น์ ๊ฐ์ฅ ๊ณ ๋ฏผ๋๋ ๊ฒ์
๐ ๋ค์ํ ์ข ๋ฅ์ ๋ฒํผ์ ํจ์จ์ ์ผ๋ก ์ ์ธํ๊ธฐ
์ฐธ๊ณ - Material UI ์ Button์ฌ์ฉ์ค์ ๋๋ค
ButtonGroup Component
์ ๋ ฅ๋ฐ๋ type์ ๋ฐ๋ฅธ Button์ ๊ฐ์ ธ์ด
interface IButton {
classes: IButtonType;
name: string;
color?: string;
icon?: JSX.Element | undefined;
}
const ButtonGroup: FC<Prop> = ({ type, name, color = 'white', icon }: Prop) => {
const classes = useButtonStyles();
return {
large: <ButtonLarge {...{ classes, name, color }} />,
medium: <ButtonMedium {...{ classes, name, color }} />,
smallFill: <ButtonSmallFill {...{ classes, name, color, icon }} />,
smallBorder: <ButtonSmallBorder {...{ classes, name, color, icon }} />,
mediumText: <ButtonMediumText {...{ classes, name, color, icon }} />,
smallText: <ButtonSmallText {...{ classes, name, color, icon }} />,
}[type] as JSX.Element;
};
export default ButtonGroup;
๊ทธ ๊ฐ๊ฐ์ Button๋ค
function ButtonLarge({ classes, name, color }: IButton) {
return (
<Button
variant="contained"
className={classes.buttonLarge}
style={{
backgroundColor: `${color}`,
color: `${color === 'white' ? '#222' : '#fff'}`,
}}
>
{name}
</Button>
);
}
์ฌ์ฉ ์
<ButtonGroup type={SMALL_FILL} name='์ด์์์ฑ' icon={<AddIcon style={{ fontSize: 16 }} />} />
์ด๋ ๊ฒ ํ์ํ ์์ฑ๋ค์ ๋ฃ์ผ๋ฉด ์ง์~ ๋ฒํผ ๋ฑ์ฅ
ํ์ง๋ง ์ด์น๊ตฌ๋ค์ type์ ๋ฌด์์ผ๊น์?
์์ ๊ฐ Button ์ปดํฌ๋ํธ๋ค์ด ๋ฐ์ props๋ IButton ์ธํฐํ์ด์ค๋ฅผ ๋ฐ๊ณ ์๋๋ฐ ๊ทธ์์ classes ๋ IButtons๋ฅผ ๋ฐ๊ณ ์๋ค.
type IButtonType = ClassNameMap<
| 'buttonLarge'
| 'buttonMedium'
| 'buttonSmallFill'
| 'buttonSmallBorder'
| 'buttonMediumText'
| 'buttonSmallText'
>;
IButtonType์ ๋ฐ๋ก ์ด๊ฒ!
useButtonStyles๋ฅผ ๋ฐ๋ก ์ด๋ ๊ฒ ๋ง๋ค์๊ณ
import { makeStyles } from '@material-ui/core/styles';
const useButtonStyles = makeStyles(() => ({
buttonLarge: {
width: '340px',
height: '64px',
fontSize: '18px',
borderRadius: '20px',
},
Material UI์ Button component๋ค์๊ฒ ๊ฐ๊ฐ์ธ์ style ์์ฑ์ ๋ถ์ฌํ๊ธฐ ์ํด
makeStyles๋ฅผ ์ฌ์ฉํ๊ณ ์ด style์ ButtonGroup์์ classes๋ก ButtonCompoent ์๊ฒ ์ ๋ฌ๋จ
ButtonGroup
const ButtonGroup: FC<Prop> = ({ type, name, color = 'white', icon }: Prop) => {
const classes = useButtonStyles();
return {
large: <ButtonLarge {...{ classes, name, color }} />,
medium: <ButtonMedium {...{ classes, name, color }} />,
//์ค๋ต
}}
function ButtonLarge({ classes, name, color }: IButton) {
return (
<Button
variant="contained"
className={classes.buttonLarge}
>
{name}
</Button>
);
}
์ด๋ ๊ฒ ์ ๋ฌ๋ฐ์์ ์คํ์ผ ์ง์ ๊ฐ๋ฅ!!!!
๐ TypeScript ์์ styled-components ์ ์์ฑ ๋๊ฒจ์ฃผ๊ธฐ
์คํ์ผ ์ปดํฌ๋ํธ์ img๋ผ๋ ํจ์๋ฅผ ๊ฐ์ง๊ณ ์๋๋ฐ
๊ทธ ์์ props๋ก ๋ฃ๊ณ ์ถ์ผ๋ฉด img๊ฐ ๊ธฐ์กด์ ๊ฐ์ง๊ณ ์๋ ์์ฑ ๋ช ์ผ๋ก props๋ฅผ ๋๊ฒจ์ฃผ์ด์ผ ํ๋ค.
ProfileImg ์ปดํฌ๋ํธ
interface ProfileImgProps {
src: string;
style: any;
}
const ProfileImg = ({ src, style }: ProfileImgProps): JSX.Element => {
return <ProfileImgStyle {...{ src, style }}></ProfileImgStyle>;
};
export default ProfileImg;
const ProfileImgStyle = styled.img`
width: ${({ style }) => `${style?.width}px`};
height: ${({ style }) => `${style?.height}px`};
border-radius: 70%;
`;
๊ฐ๋ น ์์๋ก ProfileImg์ปดํฌ๋ํธ๊ฐ width๋ฅผ ์ง์ ๋ฐ์์ ์ ์ฉํ๊ณ ์ถ๋ค๋ฉด
ProfileImg ์ props๋ style, width๋ฑ ๊ธฐ์กด img๋ผ๋ ํจ์๊ฐ ๊ฐ์ง๊ณ ์๋ ์์ฑ์ ์ฌ์ฉํด์ผ ํ๋ค.
์ฌ์ค์ styled.img ๋ผ๋๊ฑด img๋ผ๋ ํจ์๋ฅผ ๋ถ๋ฌ์ ์ฌ์ฉํ๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ๊ทธ์์์ ๋ถ๋ฅผ ์ ์๋ ์์๋ฅผ ๋ฐ์ ์ ์๋ค.
์ฌ์ฉ์
<ProfileImg style={{ width='20px', height='30px' }} />
์ด๋ฐ๋๋์ ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค.
๋๊ธ