Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3주차] 김주현 미션 제출합니다. #24

Open
wants to merge 30 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1060f71
:lipstick: [design] update UI
corinthionia Mar 22, 2022
e9b0b59
:sparkles: [feat] add handleFormSubmit
corinthionia Mar 22, 2022
f706326
:sparkles: [feat] render todo items
corinthionia Mar 22, 2022
1bd49df
:recycle: [refactor] refactor code
corinthionia Mar 23, 2022
6e72df2
:sparkles: [feat] add handleTodoClick
corinthionia Mar 23, 2022
64bd39a
:sparkles: [feat] add handleBinClick
corinthionia Mar 23, 2022
c7e5ba6
:lipstick: [design] add bin icon
corinthionia Mar 23, 2022
6c730b1
:lipstick: [design] update basic UI
corinthionia Mar 24, 2022
ac2cfa0
:sparkles: [feat] save items in local storage
corinthionia Mar 24, 2022
9e3249b
:lipstick: [design] update UI
corinthionia Mar 24, 2022
84e1de5
:recycle: [refactor] refactor code
corinthionia Mar 25, 2022
702e7b2
:recycle: [refactor] refactor code
corinthionia Mar 25, 2022
9f8a828
:lipstick: [design] add font style
corinthionia Mar 25, 2022
40d561f
:lipstick: [design] update UI
corinthionia Mar 25, 2022
baad1a9
:fire: [refactor] remove useless code
corinthionia Mar 25, 2022
3157e03
:art: [refactor] improve structure of the code
corinthionia Mar 25, 2022
0ed9384
:mute: [chore] remove logs
corinthionia Mar 25, 2022
c9b9b1d
:bug: [fix] fix a bug
corinthionia Mar 25, 2022
28ba17e
:mute: [chore] remove logs
corinthionia Mar 25, 2022
f8e3a76
:mute: [chore] remove logs
corinthionia Mar 25, 2022
5af6ec6
:recycle: [refactor] refactor code
corinthionia Mar 26, 2022
0841c66
:bug: [fix] fix a bug when submitting blank
corinthionia Mar 26, 2022
e0a8fe5
:sparkles: [feat] apply context api
corinthionia Mar 28, 2022
10c507c
:sparkles: [feat] add custom hook for managing local storage
corinthionia Mar 28, 2022
b073050
:sparkles: [feat] add custom hook for handling input text
corinthionia Mar 28, 2022
543047b
:building_construction: [refactor] convert jsx to tsx
corinthionia Mar 31, 2022
903d7aa
:bug: [fix] add any types
corinthionia Mar 31, 2022
1c47cee
:label: [chore]: update some types
corinthionia Mar 31, 2022
bf2378f
:label: [chore] update types
corinthionia Apr 1, 2022
80382ef
:recycle: [refactor] refactor code
corinthionia Apr 1, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0",
"@types/node": "^17.0.23",
"@types/react": "^17.0.43",
"@types/react-dom": "^17.0.14",
"@types/styled-components": "^5.1.24",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "5.0.0",
"styled-components": "^5.3.3",
"typescript": "^4.6.3",
"web-vitals": "^2.1.4"
},
"scripts": {
Expand All @@ -34,5 +40,9 @@
"last 1 firefox version",
"last 1 safari version"
]
}
},
"main": "index.js",
"repository": "https://github.com/corinthionia/react-todo-15th.git",
"author": "Joohyun Kim <[email protected]>",
"license": "MIT"
}
Binary file modified public/favicon.ico
Binary file not shown.
Binary file added public/img/bin.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 1 addition & 27 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,9 @@
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.

Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<title>투두리스트</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.

You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.

To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
Binary file removed public/logo192.png
Binary file not shown.
Binary file removed public/logo512.png
Binary file not shown.
25 changes: 0 additions & 25 deletions public/manifest.json

This file was deleted.

3 changes: 0 additions & 3 deletions public/robots.txt

This file was deleted.

10 changes: 0 additions & 10 deletions src/App.js

This file was deleted.

43 changes: 43 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { COLORS } from './constants/COLORS';
import { GlobalStyle } from './styles/GlobalStyle';
import styled from 'styled-components';
import InputField from './components/InputField';
import ItemList from './components/ItemList';

function App() {
return (
<>
<GlobalStyle />
<Wrapper>
<Header>투두리스트</Header>
<InputField />
<ItemList isDoneList={false} />
<ItemList isDoneList={true} />
Comment on lines +14 to +15

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

같은 컴포넌트로 할 일 목록, 완료 목록을 구분해서 생산성이 더 높은 것 같아요
저는 구분해서 두 개의 컴포넌트를 만들었는데 이런 식으로 만드니 더 좋네요.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

항상 어려운 건 설계 단계인 것 같네요
저도 많이 배워갑니다~

아래는 읽어보면 좋을 것 같은 링크입니다~
리액트 설계 가이드
컴포넌트 분리

</Wrapper>
</>
);
}

const Wrapper = styled.main`
width: 350px;
height: 700px;

border-radius: 16px;
border: 1px solid ${COLORS.border};
overflow: hidden;

background: ${COLORS.background};
`;

const Header = styled.header`
height: 10%;
font-size: 28px;
padding-left: 16px;

display: flex;
align-items: center;

border-bottom: 1px solid ${COLORS.border};
`;

export default App;
71 changes: 71 additions & 0 deletions src/components/InputField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { useContext } from 'react';
import styled from 'styled-components';
import useInput from '../hooks/useInput';
import { COLORS } from '../constants/COLORS';
import { ItemListContext } from '../contexts/ItemListContext';
import { IItem } from '../types/types';

const InputField = () => {
const { inputText, handleInputChange, resetInputText } = useInput('');
const { setItemListHandler } = useContext(ItemListContext);

const handleFormSubmit = (e: React.SyntheticEvent) => {
Copy link

@S-J-Kim S-J-Kim Apr 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const handleFormSubmit = (e: React.SyntheticEvent) => {
const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {

제가 지난주에 현재님 리뷰에 React.SyntheticEvent라는 것이 있습니다~ 라고 리뷰를 달아서 그런건지는 몰라도, 많은 분들이 이번 과제에서 이벤트 객체에 React.SyntheticEvent를 많이 사용하셨더라구요.

결론부터 말씀드리면 지양하는게 좋습니다.
저도 지난주에는 이벤트에 대해 잘 이해하지 못한 상태에서 리뷰를 달다 보니, 저 이벤트를 사용할 수 있다고 말씀을 드렸었네요. 그래서 이번주에는 해당 내용에 대해 조금 더 공부를 해보았습니다.

리액트에서 이벤트 처리를 할 때, 이벤트 핸들러에 전달받는 이벤트 객체는 자바스크립트의 네이티브 이벤트 객체가 아닙니다, SyntheticEvent 객체가 전달이 됩니다.

왜 그럴까요? SPA의 특성상 사용자와의 인터랙션이 빈번하게 발생합니다. 많은 이벤트가 발생한다는 의미죠. 하지만 그럴 때 마다 이벤트 객체를 매번 생성하게 된다면 메모리 공간과 오버헤드가 영향을 미칠 수 있겠죠. 그래서 리액트는 이벤트가 발생할 때 마다 SyntheticEvent 객체를 재사용하여 (기존에 생성되어있던 객체를 nullify하여) 이벤트를 처리하여 퍼포먼스를 향상시킵니다. (이벤트 풀링이라고도 합니다)

그래서 리액트에서는 어떤 이벤트가 발생하던 간에, SyntheticEvent 객체를 호출하고, 여기에 객체의 속성으로 원래 네이티브 이벤트를 알려주는 방식을 사용합니다. 감이 오시나요? SyntheticEvent를 사용하는 것은 결국 어떤 이벤트가 와도 괜찮다고 말하는 것과 같죠.

따라서, 만약 다중 이벤트를 폼에서 처리해야 한다면, 해당하는 이벤트들 끼리 묶어 하나의 커스텀 타입을 만드는 것이 더욱 좋은 방법일 것 같습니다.

SyntheticEvent

e.preventDefault();

if (inputText.replace(/\s+/g, '')) {
const todo = { id: Date.now(), text: inputText, isDone: false };
setItemListHandler((itemList: IItem[]) => [todo, ...itemList]);
} else {
alert('할일을 입력해 주세요');
}

resetInputText();
};

return (
<InputForm onSubmit={handleFormSubmit}>
<Input
value={inputText}
onChange={handleInputChange}
spellCheck="false"
autoFocus
placeholder="할일을 입력하세요"
/>
<SubmitBtn onClick={handleFormSubmit}>➕</SubmitBtn>
</InputForm>
);
};

const InputForm = styled.form`
height: 10%;
display: flex;
align-items: center;
justify-content: space-evenly;
`;

const Input = styled.input`
width: 77.5%;
height: 50%;
padding-left: 2.5%;

border-radius: 8px;
border: 1px solid ${COLORS.border};
background: ${COLORS.background};

::placeholder {
color: ${COLORS.placeholder};
}
`;

const SubmitBtn = styled.button`
width: 10%;
height: 50%;

background: none;
border-radius: 8px;
border: 1px solid ${COLORS.border};

cursor: pointer;
`;

export default InputField;
53 changes: 53 additions & 0 deletions src/components/Item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import styled, { css } from 'styled-components';
import { COLORS } from '../constants/COLORS';
import { IIsDoneList } from '../types/types';

const Item = ({
id,
text,
isDoneList,
handleTextClick,
handleDeleteBtnClick,
}) => {
return (
<ItemWrapper key={id}>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 여기서 key값을 줘도 되는군요 처음 알았네요

<ItemText id={id} isDoneList={isDoneList} onClick={handleTextClick}>
{text}
</ItemText>
<DeleteBtn
src={`${process.env.PUBLIC_URL}/img/bin.png`}
id={id}
onClick={handleDeleteBtnClick}
/>
</ItemWrapper>
);
};

const ItemWrapper = styled.div`
width: 90%;
margin: 12px;

display: flex;
align-items: center;
`;

const ItemText = styled.span<IIsDoneList>`
${({ isDoneList }) =>
isDoneList &&
css`
color: ${COLORS.lightgrey};
text-decoration: line-through;
`}

cursor: pointer;
`;

const DeleteBtn = styled.img`
width: 16px;
height: 16px;
margin-left: 8px;

cursor: pointer;
`;

export default Item;
94 changes: 94 additions & 0 deletions src/components/ItemList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { useContext } from 'react';
import styled from 'styled-components';
import Item from './Item';
import { COLORS } from '../constants/COLORS';
import { ItemListContext } from '../contexts/ItemListContext';
import { IItem } from '../types/types';

const ItemList = ({ isDoneList }) => {
const { itemList, setItemListHandler } = useContext(ItemListContext);

const filteredList = itemList.filter(
(item: IItem) => item.isDone === isDoneList
);

const handleTextClick = (e: React.MouseEvent<HTMLButtonElement>) => {
const textElement: HTMLButtonElement = e.currentTarget;

const newList = (filteredList: IItem[]) =>
filteredList.map((item: IItem) =>
item.id === parseInt(textElement.id)
? { ...item, isDone: !item.isDone }
: item
);

setItemListHandler(newList);
};
Comment on lines +15 to +26
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분 되게 신기하게 작성하셨네요
두 번 읽고 이해했습니다...
대단하시네용


const handleDeleteBtnClick = (e: React.MouseEvent<HTMLButtonElement>) => {
const deleteBtn: HTMLButtonElement = e.currentTarget;

const newList = (filteredList: IItem[]) =>
filteredList.filter((todo: IItem) => todo.id !== parseInt(deleteBtn.id));

setItemListHandler(newList);
};

return (
<>
<ListTitle>
{isDoneList
? `${filteredList.length}개의 할일을 완료했어요`
: `${filteredList.length}개의 할일이 남아있어요`}
</ListTitle>
<List>
{filteredList.map(({ id, text }) => (
<Item
key={id}
id={id}
text={text}
isDoneList={isDoneList}
handleTextClick={handleTextClick}
handleDeleteBtnClick={handleDeleteBtnClick}
/>
))}
</List>
</>
);
};

const ListTitle = styled.div`
height: 7.5%;
padding-left: 16px;

display: flex;
align-items: center;

font-size: 20px;

border-top: 1px solid ${COLORS.border};
border-bottom: 1px solid ${COLORS.border};
`;

const List = styled.section`
height: 32.5%;

display: flex;
flex-direction: column;
align-items: center;

overflow: auto;

&::-webkit-scrollbar {
width: 0.75rem;
}

&::-webkit-scrollbar-thumb {
background: ${COLORS.scrollbar};
background-clip: padding-box;
border-radius: 10px;
border: 0.25rem solid transparent;
}
`;

export default ItemList;
8 changes: 8 additions & 0 deletions src/constants/COLORS.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const COLORS = {
border: '#ececec',
darkgrey: '#3d3d3d',
lightgrey: 'rgba(255, 255, 255, 0.3)',
placeholder: 'rgba(255, 255, 255, 0.7)',
scrollbar: 'rgba(255, 255, 255, 0.4)',
background: 'rgba(255, 255, 255, 0.1)',
};
Loading