[React] 2주차 개발일지

79249 단어 ReactReact

스파르팅코딩클럽 '프론트엔드의 꽃, 리액트' 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 기능을 그냥 구현하였다.

  • 결과 화면

포뇨 이미지를 좌우로 스와이프 할 때마다 문제가 바뀌며, 모든 문제가 끝났을 때 '퀴즈 끝!' 문구가 나오게 됨.

좋은 웹페이지 즐겨찾기