[React] 2주차 개발일지
스파르팅코딩클럽 '프론트엔드의 꽃, 리액트' 2주차 강의를 듣고 작성한 글입니다.
SCSS
: CSS를 편하게 쓰도록 도와주는 style.
필요한 모듈 설치 :
yarn add [email protected] open-color sass-loader classnames
- 문자열 치환 가능, "&"으로 상위 요소에 이어 쓰기 가능
$defaultSize: 20px;
$className: blue;
p{
font-size: #{$defaultSize};
&.#{$className} {color: #{$className}}
}
div {
background-color: green
&:hover { background-color: blue }
}
styled-components
: 컴포넌트 스타일링 기법. (class 이름 고민할 필요 X)
- 패키지 설치 : yarn add styled-components
const MyStyled = styled.div`
width: 50vw;
min-height: 150px;
padding: 10px;
border-radius: 15px;
color: #fff;
&:hover{
background-color: #ddd;
}
background-color: ${(props) => (props.bgColor를 ? "red" : "purple")};
`;
export default App;
라이프 사이클
: 컴포넌트가 렌더링을 준비하는 순간 ~ 페이지에서 사라질 때까지
- 컴포넌트: 생성 → 수정(업데이트) → 사라짐(제거)
라이프 사이클 함수는 클래스형 컴포넌트에서만 사용 가능.
- constructor() : 생성자 함수. 컴포넌트 생성시 가장 처음으로 호출.
- render() : 컴포넌트 모양 정의
- componentDidMount() : 컴포넌트가 화면에 나타나도록 함.
- componentDidUpdate(prevProps, prevState, snapshot) : 리렌더링을 완료한 후 실행되는 함수
- componentWillUnmount() : 컴포넌트가 DOM에서 제거 될 때 실행하는 함수
Ref
- 리액트에서 돔요소를 가져오려면 React.createRef() 사용.
State 관리
- setState() : 클래스형 컴포넌트의 state를 업데이트할 때 사용하는 함수.
Event Listener
: componentDidMount()에 넣어주면 됨.
[Toy Project] Nemo Project
- App.js
import React from "react";
import Nemo from "./Nemo";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.div = React.createRef();
}
hoverEvent = (e) => {
console.log(e);
console.log(e.target);
e.target.style.background = "#eee";
};
componentDidMount() {
this.div.current.addEventListener("mouseover", this.hoverEvent);
}
componentWillUnmount() {
this.div.current.removeEvenListener("mouseover", this.hoverEvent);
}
render() {
return (
<div className="App" ref={this.div}>
<Nemo />
</div>
);
}
}
export default App;
- Nemo.js
import React from "react";
const Nemo = (props) => {
const [count, setCount] = React.useState(3);
console.log(count);
const addNemo = () => {
setCount(count + 1);
};
const removeNemo = () => {
setCount(count > 0 ? count - 1 : 0);
};
const nemo_count = Array.from({ length: count }, (v, i) => i);
return (
<div className="App">
{nemo_count.map((num, idx) => {
return (
<div
key={idx}
style={{
width: "150px",
height: "150px",
backgroundColor: "#ddd",
margin: "10px",
}}
>
nemo
</div>
);
})}
<div>
<button onClick={addNemo}>하나 추가</button>
<button onClick={removeNemo}>하나 빼기</button>
</div>
</div>
);
};
export default Nemo;
- 결과 화면
[Toy Project] BucketList에 아이템 추가
- App.js
import React from "react";
import BucketList from "./BucketList";
import styled from "styled-components";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
list: ["영화관 가기", "매일 책읽기", "수영 배우기"],
};
this.text = React.createRef();
}
addBucketList = () => {
let list = this.state.list;
const new_item = this.text.current.value;
this.setState({ list: [...list, new_item] });
};
componentDidMount() {
console.log(this.text);
console.log(this.text.current);
}
render() {
return (
<div className="App">
<Container>
<Title>내 버킷리스트</Title>
<Line />
<BucketList list={this.state.list} />
</Container>
<Input>
<input type="text" ref={this.text} />
<button onClick={this.addBucketList}>추가하기</button>
</Input>
</div>
);
}
}
const Input = styled.div`
max-width: 350px;
min-height: 10vh;
background-color: #fff;
padding: 16px;
margin: 20px auto;
border-radius: 5px;
border: 1px solid #ddd;
`;
const Container = styled.div`
max-width: 350px;
min-height: 80vh;
background-color: #fff;
padding: 16px;
margin: 20px auto;
border-radius: 5px;
border: 1px solid #ddd;
`;
const Title = styled.h1`
color: slateblue;
text-align: center;
`;
const Line = styled.hr`
margin: 16px 0px;
border: 1px dotted #ddd;
`;
export default App;
- BucketList.js
import React from "react";
import styled from "styled-components";
const BucketList = (props) => {
const my_lists = props.list;
return (
<ListStyle>
{my_lists.map((list, index) => {
return <ItemStyle key={index}>{list}</ItemStyle>;
})}
</ListStyle>
);
};
const ListStyle = styled.div`
display: flex;
flex-direction: column;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
`;
const ItemStyle = styled.div`
padding: 16px;
margin: 8px;
background-color: aliceblue;
`;
export default BucketList;
- 결과 화면
[HW] FriendTest Project
- App.js
import React from "react";
import "./App.css";
import Start from "./Start";
import Quiz from "./Quiz";
import Score from "./Score";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "포뇨",
page: "quiz",
scoreMsg: "아주 친한 친구 사이:)",
list: [
{ question: "포뇨는 5살이다.", answer: "O" },
{ question: "포뇨는 검정색 머리다.", answer: "X" },
{ question: "포뇨는 물을 좋아한다.", answer: "O" },
{ question: "포뇨는 라면을 싫어한다.", answer: "" },
],
ranking: [
{ rank: 1, name: "최수빈", message: "안녕, 포뇨야!" },
{ rank: 2, name: "배인혁", message: "포뇨 HI~" },
{ rank: 3, name: "김원필", message: "안녕, 포뇨!" },
{ rank: 4, name: "송건희", message: "포뇨포뇨" },
],
};
}
render() {
return (
<div className="App">
{this.state.page === "quiz" && <Quiz list={this.state.list} />}
{this.state.page === "start" && <Start name={this.state.name} />}
{this.state.page === "score" && (
<Score name={this.state.name} scoreMsg={this.state.scoreMsg} />
)}
</div>
);
}
}
export default App;
- Score.js
import React from "react";
import styled from "styled-components";
const Score = (props) => {
return (
<ScoreContainer>
<Text>
<span>{props.name}</span> 퀴즈에
<br />
대한 내 점수는?
</Text>
<MyScore>
<span>100</span>점<p>{props.scoreMsg}</p>
</MyScore>
<Button>랭킹보기</Button>
</ScoreContainer>
);
};
const ScoreContainer = styled.div`
display: flex;
width: 100vw;
height: 100vh;
overflow: hidden;
padding: 20px;
box-sizing: border-box;
flex-direction: column; //세로로 객체 배열
justify-content: center;
align-items: center;
`;
const Text = styled.h1`
font-size: 1.5em;
margin: 0px;
line-height: 1.7;
text-align: center;
& span {
background-color: #ffe08c;
padding: 5px 10px;
border-radius: 30px;
}
`;
const MyScore = styled.div`
& span {
border-radius: 25px;
padding: 5px 10px;
background-color: #ffe08c;
}
font-weight: 600;
font-size: 2em;
margin: 25px;
text-align: center;
& > p {
margin: 20px 0px;
font-size: 18px;
font-weight: 550;
}
`;
const Button = styled.button`
color: white;
padding: 10px 20px;
background-color: #6799ff;
border-radius: 30px;
margin: 10px;
border: 1px solid #b2ccff;
width: 70vw;
`;
export default Score;
- Quiz.js
import React from "react";
import styled from "styled-components";
import img from "./ponyo.jpg";
import TinderCard from "react-tinder-card";
// import SwipeItem from "./SwiptItem";
const Quiz = (props) => {
const [num, setNum] = React.useState(0);
const onSwipe = (direction) => {
console.log("You swiped: " + direction);
setNum(num + 1);
};
if (num > 3) {
return <Finish>퀴즈 끝!</Finish>;
}
return (
<QuizContainer>
<p>
<span>{num + 1}번 문제</span>
</p>
{props.list.map((l, idx) => {
if (num === idx) {
return <Question key={idx}>{l.question}</Question>;
}
})}
<AnswerZone>
<Answer>O</Answer>
<Answer>X</Answer>
</AnswerZone>
{props.list.map((l, idx) => {
if (idx === num) {
return (
<DragItem key={idx}>
<TinderCard
onSwipe={onSwipe}
onCardLeftScreen={onSwipe}
onCardRightScreen={onSwipe}
preventSwipe={["up", "down"]}
>
<img src={img} />
</TinderCard>
</DragItem>
// <SwipeItem key={idx} onSwipe={onSwipe} />;
);
}
})}
</QuizContainer>
);
};
const QuizContainer = styled.div`
text-align: center;
margin-top: 130px;
& > p > span {
padding: 8px 16px;
background-color: #ffe08c;
border-radius: 30px;
font-weight: bold;
}
`;
const Question = styled.h1`
font-size: 1.5em;
`;
const AnswerZone = styled.div`
width: 100vw;
height: 100vh;
display: flex;
position: absolute;
top: 0;
left: 0;
z-index: 1;
`;
const Answer = styled.div`
width: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 100px;
font-weight: 600;
color: #dadafc77;
`;
const DragItem = styled.div`
display: flex;
align-items: center;
justify-content: center;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 10;
& img {
max-width: 130px;
}
`;
const Finish = styled.p`
text-align: center;
margin-top: 300px;
font-weight: 600;
font-size: 30px;
`;
export default Quiz;
- SwipeItem.js
import React from "react";
import styled from "styled-components";
import img from "./ponyo.jpg";
const SwipeItem = React.memo(({ onSwipe }) => {
const swipe_div = React.useRef(null);
let swipe_status = "ready";
let target_classname = "";
let coordinate = {
start_x: 0,
start_y: 0,
end_x: 0,
end_y: 0,
};
React.useEffect(() => {
const reset = () => {
console.log("in reset");
swipe_status = "ready";
coordinate = {
start_x: 0,
start_y: 0,
end_x: 0,
end_y: 0,
};
swipe_div.current.className = target_classname;
swipe_div.current.style.left = 0 + "px";
swipe_div.current.style.top = 0 + "px";
};
const touchStart = (e) => {
swipe_status = "touchstart";
target_classname = swipe_div.current.className;
coordinate = {
...coordinate,
start_x: e.touches[0].clientX,
start_y: e.touches[0].clientY,
};
};
const touchEnd = (e) => {
swipe_status = "touchend";
coordinate = {
...coordinate,
end_x: e.changedTouches[0].clientX,
end_y: e.changedTouches[0].clientY,
};
let diff_x = coordinate.end_x - coordinate.start_x;
let direct = "left";
if (Math.abs(diff_x) > 50) {
swipe_div.current.className = target_classname + " swipe";
}
if (diff_x > 0) {
direct = "right";
swipe_div.current.style.left = diff_x + 150 + "px";
swipe_div.current.style.opacity = 0;
} else {
direct = "left";
swipe_div.current.style.left = diff_x - 150 + "px";
swipe_div.current.style.opacity = 0;
}
window.setTimeout(() => {
reset();
onSwipe(direct);
}, 300);
return;
};
const touchMove = (e) => {
swipe_status = "toucmove";
e.preventDefault();
let current_coordinate = {
x: e.touches[0].clientX,
y: e.touches[0].clientY,
};
swipe_div.current.style.left =
current_coordinate.x - coordinate.start_x + "px";
swipe_div.current.style.top =
current_coordinate.y - coordinate.start_y + "px";
};
const touchCancel = (e) => {
swipe_status = "touchcancel";
reset();
};
swipe_div.current.addEventListener("touchstart", touchStart);
swipe_div.current.addEventListener("touchend", touchEnd);
swipe_div.current.addEventListener("touchmove", touchMove);
swipe_div.current.addEventListener("touchcancel", touchCancel);
return () => {
if (!swipe_div.current) {
return;
}
swipe_div.current.removeEventListener("touchstart", touchStart);
swipe_div.current.removeEventListener("touchend", touchEnd);
swipe_div.current.removeEventListener("touchmove", touchMove);
swipe_div.current.removeEventListener("touchcancel", touchCancel);
};
}, []);
return (
<DragItem ref={swipe_div}>
<img src={img} />
</DragItem>
);
});
const DragItem = styled.div`
display: flex;
align-items: center;
justify-content: center;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
&.swipe {
transition: 300ms;
}
& > div {
border-radius: 500px;
background-color: #ffd6aa;
}
& img {
max-width: 130px;
}
`;
SwipeItem.defaultProps = {
onSwipe: () => {},
};
export default SwipeItem;
❓ Quiz.js에 SwiptItem.js를 import해서 실행했을 경우 포뇨 이미지는 잘 뜨는데 swipe 기능이 아예 먹히질 않는다,,,
❓ 무엇이 문제인지 해결하지 못해서 SwipeItem.js import 하지 않고 Quiz.js 내에서 TinderCard를 사용하여 Swipe 기능을 그냥 구현하였다.
- 결과 화면
포뇨 이미지를 좌우로 스와이프 할 때마다 문제가 바뀌며, 모든 문제가 끝났을 때 '퀴즈 끝!' 문구가 나오게 됨.
Author And Source
이 문제에 관하여([React] 2주차 개발일지), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@tnqls1211v/React-week2-devlog저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)