styled-components 홈페이지

 

styled-components

CSS for the <Component> Age

styled-components.com

설치

npm i styled-components

tip: vscode-styled-components

vscode extension인 vscode-styled-components를 받으면 자동 완성 및 하이라이트 기능 제공

 

without styled-components

 

function App() {
  return (
    <div style={{ display: "flex" }}>
      <div style={{ backgroundColor: "teal", width: 100, height: 100 }}></div>
      <div style={{ backgroundColor: "tomato", width: 100, height: 100 }}></div>
    </div>
  );
}

export default App;

 

with styled-components

  • styled 뒤에 속성 값을 back tick 사이에 입력
  • back tick 안에 들어간 내용은 일반 css로 작성
  • 실제 페이지에서 검사도구로 확인할 경우, 임의의 class 이름이 부여된 것을 확인 가능
import styled from "styled-components";

const Father = styled.div`
  display: flex;
`;

const BoxOne = styled.div`
  background-color: teal;
  width: 100px;
  height: 100px;
`;

const BoxTwo = styled.div`
  background-color: tomato;
  width: 100px;
  height: 100px;
`;

function App() {
  return (
    <Father>
      <BoxOne />
      <BoxTwo />
    </Father>
  );
}

export default App;

Problem

styled-components내에 중복되는 부분을 어떻게 효율적으로 작성할 수 있을까?

Scratch1; component에 props 받기

import styled from "styled-components";

const Father = styled.div`
  display: flex;
`;

const Box = styled.div`
  background-color: ${(props) => props.bgColor};
  width: 100px;
  height: 100px;
`;

function App() {
  return (
    <Father>
      <Box bgColor="teal" />
      <Box bgColor="tomato" />
    </Father>
  );
}

export default App;
```

Scratch 2; 기존의 styled-component 상속

상속 전
import styled from "styled-components";

const Father = styled.div`
  display: flex;
`;

const Box = styled.div`
  background-color: ${(props) => props.bgColor};
  width: 100px;
  height: 100px;
`;

const Circle = styled.div`
  background-color: ${(props) => props.bgColor};
  width: 100px;
  height: 100px;
  border-radius: 9999px;
`;

function App() {
  return (
    <Father>
      <Box bgColor="teal" />
      <Circle bgColor="tomato" />
    </Father>
  );
}

export default App;

 

상속 후: Cicle에서 기존 Box를 상속 받음
import styled from "styled-components";

const Father = styled.div`
  display: flex;
`;

const Box = styled.div`
  background-color: ${(props) => props.bgColor};
  width: 100px;
  height: 100px;
`;

const Circle = styled(Box)`
  border-radius: 9999px;
`;

function App() {
  return (
    <Father>
      <Box bgColor="teal" />
      <Circle bgColor="tomato" />
    </Father>
  );
}

export default App;

Scratch3; as

as는 기존의 만들어진 styled-components를 html tag만 변경하여 사용할 수있게 해준다
import styled from "styled-components";

const Father = styled.div`
  display: flex;
`;

const Btn = styled.button`
  color: white;
  background-color: tomato;
  border: 0;
  border-radius: 15px;
`;

function App() {
  return (
    <Father>
      <Btn>Log-in</Btn>
      <Btn as="a">Log-in</Btn>
    </Father>
  );
}

export default App;

Scratch 4; attrs

attrs를 사용하여 각 styled-components에 속성 부여를 해준다
import styled from "styled-components";

const Father = styled.div`
  display: flex;
`;

const Input = styled.input.attrs({ required: true, minLength: 10 })`
  background-color: tomato;
`;

function App() {
  return (
    <Father>
      <Input />
      <Input />
      <Input />
      <Input />
    </Father>
  );
}

export default App;

keyframe

keyframe을 사용한 애니메이션 적용 예시

import styled, { keyframes } from "styled-components";

const Wrapper = styled.div`
  display: flex;
`;

const rotationAnimaiton = keyframes`
  0% {
    transform: rotate(0deg);
    border-radius: 0px;
  }
  50% {
    transform: rotate(720deg);
    border-radius: 100px;
  }
  100% {
    transform: rotate(0deg);
    border-radius: 0px;
  }
`;

const Box = styled.div`
  height: 200px;
  width: 200px;
  background-color: salmon;
  animation: ${rotationAnimaiton} 1s linear infinite;
`;

function App() {
  return (
    <Wrapper>
      <Box />
    </Wrapper>
  );
}

export default App;

Pseudoelements, pseudoselectors, and nesting

styled-components 안에서 일반 html tag를 선택할 수 있음
styled-components 안에서 pseudo selector를 선택할 수 있음

 

import styled from "styled-components";

const Wrapper = styled.div`
  display: flex;
`;

const Box = styled.div`
  height: 200px;
  width: 200px;
  background-color: salmon;
  display: flex;
  justify-content: center;
  align-items: center;
  span {
    font-size: 36px;
    &:hover {
      font-size: 72px;
    }
  }
`;

function App() {
  return (
    <Wrapper>
      <Box>
        <span>🤪</span>
      </Box>
    </Wrapper>
  );
}

export default App;
styled-components 안에서 styled-components를 선택할 수도 있음
import styled, { keyframes } from "styled-components";

const Wrapper = styled.div`
  display: flex;
`;

const rotationAnimaiton = keyframes`
  0% {
    transform: rotate(0deg);
    border-radius: 0px;
  }
  50% {
    transform: rotate(360deg);
    border-radius: 100px;
  }
  100% {
    transform: rotate(0deg);
    border-radius: 0px;
  }
`;

const Emoji = styled.span`
  font-size: 36px;
`;

const Box = styled.div`
  height: 200px;
  width: 200px;
  background-color: salmon;
  display: flex;
  justify-content: center;
  align-items: center;
  animation: ${rotationAnimaiton} 1s linear infinite;
  ${Emoji} {
    &:hover {
      font-size: 72px;
    }
  }
`;

function App() {
  return (
    <Wrapper>
      <Box>
        <Emoji>🤪</Emoji>
      </Box>
    </Wrapper>
  );
}

export default App;

theme

  • 기본적으로 모든 색상을 가지고 있는 object
  • local estate management와 응용하면 다크모드 구현 가능
  • App을 ThemeProvider로 감싸줌
  • theme 간의 property 이름은 같아야함
// index.js
import React from "react";
import ReactDOM from "react-dom/client";
import { ThemeProvider } from "styled-components";
import App from "./App";

const darkTheme = {
  textColor: "whitesmoke",
  backgroundColor: "#111",
};

const lightTheme = {
  textColor: "#111",
  backgroundColor: "whitesmoke",
};

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <ThemeProvider theme={darkTheme}>
    <App />
  </ThemeProvider>
);
ThemeProvider의 값에 접근 가능
// App.js
import styled from "styled-components";

const Wrapper = styled.div`
  background-color: ${(props) => props.theme.backgroundColor};
`;

const Title = styled.h1`
  color: ${(props) => props.theme.textColor};
`;

function App() {
  return (
    <Wrapper>
      <Title>Title</Title>
    </Wrapper>
  );
}

export default App;

'React' 카테고리의 다른 글

[React] 가상화폐시세 사이트 1 - CSS reset  (0) 2023.08.29
[React] TypeScript  (0) 2023.08.17
[React 기초] 08. 예제 - Movie info  (0) 2023.08.10
[React 기초] 07. 예제 - Coin tracker  (0) 2023.08.10
[React 기초] 06. 예제 - Todo List  (0) 2023.08.10

영화 정보 api

 

API Documentation - YTS YIFY

Official YTS YIFY API documentation. YTS offers free API - an easy way to access the YIFY movies details.

yts.mx

아래 조건을 사용한 api를 사용함

https://yts.mx/api/v2/list_movies.json?minimum_rating=8.5&sort_by=year

import { useEffect, useState } from "react";

function App() {
  const [loading, setLoading] = useState(true);
  const [movies, setMovies] = useState([]);
  const getMovie = async () => {
    const response = await fetch(
      "https://yts.mx/api/v2/list_movies.json?minimum_rating=8.5&sort_by=year"
    );
    const json = await response.json();
    // 아래 같이 한줄로 작성해도 됨
    // const json = await (
    //   await fetch(
    //     "https://yts.mx/api/v2/list_movies.json?minimum_rating=8.5&sort_by=year"
    //   )
    // ).json();
    setMovies(json.data.movies);
    setLoading(false);
  };
  useEffect(() => {
    getMovie();
  }, []);
  return (
    <div>
      {loading ? (
        <h1>Loading...</h1>
      ) : (
        <div>
          {movies.map((movie) => (
            <div key={movie.id}>
              <h2>{movie.title}</h2>
              <img src={movie.medium_cover_image}></img>
              <p>{movie.summary}</p>
              <ul>
                {/* hasOwnProperty를 사용하여 movie 내에 genres 프로퍼티가 있는지 없는지 확인할 수 있음 */}
                {movie.hasOwnProperty("genres")
                  ? movie.genres.map((genre) => <li key={genre}>{genre}</li>)
                  : null}
              </ul>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

export default App;

컴포넌트 분리

Movie.js 파일 생성
// Movie.js
function Movie({ title, coverImg, summary, genres }) {
  return (
    <div>
      <h2>{title}</h2>
      <img src={coverImg} alt={title}></img>
      <p>{summary}</p>
      <ul>
        {genres.map((genre) => (
          <li key={genre}>{genre}</li>
        ))}
      </ul>
    </div>
  );
}

export default Movie;
App.js에서 Movie 컴포넌트 사용
// App.js
import { useEffect, useState } from "react";
import Movie from "./Movie";

function App() {
  const [loading, setLoading] = useState(true);
  const [movies, setMovies] = useState([]);
  const getMovie = async () => {
    const response = await fetch(
      "https://yts.mx/api/v2/list_movies.json?minimum_rating=8.5&sort_by=year"
    );
    const json = await response.json();
    setMovies(json.data.movies);
    setLoading(false);
  };
  useEffect(() => {
    getMovie();
  }, []);
  return (
    <div>
      {loading ? (
        <h1>Loading...</h1>
      ) : (
        <div>
          {movies.map((movie) => (
            <Movie
              key={movie.id}
              title={movie.title}
              coverImg={movie.medium_cover_image}
              summary={movie.summary}
              genres={movie.genres}
            />
          ))}
        </div>
      )}
    </div>
  );
}

export default App;

입력한 props의 타입 검증을 위한 PropTypes 적용

// Movie.js
import PropTypes from "prop-types";

Movie.propTypes = {
  title: PropTypes.string.isRequired,
  coverImg: PropTypes.string.isRequired,
  summary: PropTypes.string,
  genres: PropTypes.arrayOf(PropTypes.string),
};

React Router

5.3.0 버전을 사용
react router 설치
npm i react-router-dom@5.3.0

이제 스크린 (Router) 단위로 생각

App.js는 route를 받아서 스크린을 불러옴

스크린(Router) 단위로 파일 생성

src 경로 안의 파일 정리

파일 경로 새로 생성

  • components/Movie.js (경로를 옮겼기 때문에 import의 경로 수정)
  • routes/Home.js
  • routes/Detail.js
기존 App.js에 있던 내용을 Home.js로 옮겨줌
// Home.js
import { useEffect, useState } from "react";
import Movie from "./components/Movie";

function Home() {
  const [loading, setLoading] = useState(true);
  const [movies, setMovies] = useState([]);
  const getMovie = async () => {
    const response = await fetch(
      "https://yts.mx/api/v2/list_movies.json?minimum_rating=8.5&sort_by=year"
    );
    const json = await response.json();
    setMovies(json.data.movies);
    setLoading(false);
  };
  useEffect(() => {
    getMovie();
  }, []);
  return (
    <div>
      {loading ? (
        <h1>Loading...</h1>
      ) : (
        <div>
          {movies.map((movie) => (
            <Movie
              key={movie.id}
              title={movie.title}
              coverImg={movie.medium_cover_image}
              summary={movie.summary}
              genres={movie.genres}
            />
          ))}
        </div>
      )}
    </div>
  );
}

export default Home;
App에서 router 적용
// App.js
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Home from "./routes/Home";
import Movie from "./routes/Detail";

function App() {
  return (
    <Router>
      <Switch>
        <Route path="/movie">
          <Movie />
        </Route>
        <Route path="/">
          <Home />
        </Route>
      </Switch>
    </Router>
  );
}

export default App;
  • 위 코드에서 "/moive" 가 "/" Route보다 아래에 있으면 "/movie" 주소를 입력해도 "/" Route가 먼저 render된다. 위의 코드처럼 router의 위치를 조종하거나 exact={true}를 적용
  • Switch 태그는 Route를 한 번에 하나만 render할 때 사용

페이지 이동

<a> 태그 사용하기

  • 사용 가능
  • 다만 해당 페이지로 이동했을 경우 전체 페이지가 다시 rendering
<h2>
  <a href="/movie">{title}</a>
</h2>

<Link> 사용하기

  • 브라우저의 새로고침 없이 다른 페이지로 이동시켜주는 컴포넌트
// Movie.js
import { Link } from "react-router-dom";

<h2>
  <Link to="/movie">{title}</Link>
</h2>;

Dynamic URL

  • 변화하는 주소를 받을 수 있음
  • 변화하는 부분에 ":" 를 사용
// App.js
<Route path="/movie/:id">
  <Detail />
</Route>
id 값을 사용하기 위해서 props에서 id 전달
// Home.js
{
  movies.map((movie) => (
    <Movie
      key={movie.id}
      id={movie.id}
      title={movie.title}
      coverImg={movie.medium_cover_image}
      summary={movie.summary}
      genres={movie.genres}
    />
  ));
}
전달 받은 props 값 Link에 적용
// Movie.js
function Movie({ title, id, coverImg, summary, genres }) {
  return (
    <div>
      <h2>
        <Link to={`/movie/${id}`}>{title}</Link>
      </h2>
      <img src={coverImg} alt={title}></img>
      <p>{summary}</p>
      <ul>
        {genres.map((genre) => (
          <li key={genre}>{genre}</li>
        ))}
      </ul>
    </div>
  );
}

useParams

url에 있는 값을 반환해주는 함수

// Detail.js
import { useParams } from "react-router-dom";

function Detail() {
  const x = useParams();
  console.log(x);
  // {id: '53703'}
  // id: "53703"
  // [[Prototype]]: Object
  return <h1>Detail</h1>;
}

export default Detail;

Detail.js 내에서 api 받아오기

// Detail.js
import { useParams } from "react-router-dom";
import { useEffect } from "react";

function Detail() {
  const { id } = useParams();
  const getMovie = async () => {
    const json = await (
      await fetch(`https://yts.mx/api/v2/movie_details.json?movie_id=${id}`)
    ).json();
    // console.log(json);
  };
  useEffect(() => {
    getMovie();
  }, []);
  return <h1>Detail</h1>;
}

export default Detail;

최종 코드

// App.js
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Home from "./routes/Home";
import Detail from "./routes/Detail";

function App() {
  return (
    <Router>
      <Switch>
        <Route path="/movie/:id">
          <Detail />
        </Route>
        <Route path="/">
          <Home />
        </Route>
      </Switch>
    </Router>
  );
}

export default App;

 

// Detail.js
import { useParams } from "react-router-dom";
import { useEffect, useState } from "react";
import MovieDetail from "../components/MovieDetail";

function Detail() {
  const { id } = useParams();
  const [loading, setLoading] = useState(true);
  const [movie, setMovie] = useState("");
  const getMovie = async () => {
    const json = await (
      await fetch(`https://yts.mx/api/v2/movie_details.json?movie_id=${id}`)
    ).json();
    // console.log(json);
    setLoading(false);
    setMovie(json.data.movie);
  };
  useEffect(() => {
    getMovie();
  }, []);

  return (
    <div>
      <h1>Detail</h1>
      {loading ? (
        <h1>loading</h1>
      ) : (
        <MovieDetail
          date_uploaded={movie.date_uploaded}
          download_count={movie.download_count}
          genres={movie.genres}
          language={movie.language}
          like_count={movie.like_count}
          large_cover_image={movie.large_cover_image}
          rating={movie.rating}
          runtime={movie.runtime}
          title={movie.title}
          torrents={movie.torrents}
          year={movie.year}
          description={movie.description_full}
        />
      )}
    </div>
  );
}

export default Detail;
// MovieDetail.js
import PropTypes from "prop-types";

function MovieDetail({
  date_uploaded,
  download_count,
  genres,
  language,
  like_count,
  large_cover_image,
  rating,
  runtime,
  title,
  year,
  description,
  torrents,
}) {
  return (
    <div>
      <h2>{title}</h2>
      <img src={large_cover_image} alt={title}></img>
      <p>Release: {year}</p>
      <p>Language: {language}</p>
      <p>Runtime: {runtime}</p>
      <p>Rating: {rating}</p>
      <p>Like Count: {like_count}</p>
      <p>Genre</p>
      <ul>
        {genres.map((genre) => (
          <li key={genre}>{genre}</li>
        ))}
      </ul>
      <p>{description}</p>
      <hr />
      <h3>Download info</h3>
      <p>File Uploaded: {date_uploaded}</p>
      <p>Download Count: {download_count}</p>
      <ul>
        {torrents.map((torrent, i) => (
          <li key={torrent.hash}>
            <a href={torrent.url}>다운로드 링크 {i + 1}</a>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default MovieDetail;
// Home.js

import { useEffect, useState } from "react";
import Movie from "../components/Movie";

function Home() {
  const [loading, setLoading] = useState(true);
  const [movies, setMovies] = useState([]);
  const getMovie = async () => {
    const response = await fetch(
      "https://yts.mx/api/v2/list_movies.json?minimum_rating=8.5&sort_by=year"
    );
    const json = await response.json();
    setMovies(json.data.movies);
    setLoading(false);
  };
  useEffect(() => {
    getMovie();
  }, []);
  return (
    <div>
      {loading ? (
        <h1>Loading...</h1>
      ) : (
        <div>
          {movies.map((movie) => (
            <Movie
              key={movie.id}
              id={movie.id}
              title={movie.title}
              coverImg={movie.medium_cover_image}
              summary={movie.summary}
              genres={movie.genres}
            />
          ))}
        </div>
      )}
    </div>
  );
}

export default Home;
// Movie.js

import PropTypes from "prop-types";
import { Link } from "react-router-dom";

function Movie({ title, id, coverImg, summary, genres }) {
  return (
    <div>
      <h2>
        <Link to={`/movie/${id}`}>{title}</Link>
      </h2>
      <img src={coverImg} alt={title}></img>
      {/* summary 길이에 따른 처리 */}
      <p>{summary.length > 235 ? `${summary.slice(0, 235)} ...` : summary}</p>
      <ul>
        {genres.map((genre) => (
          <li key={genre}>{genre}</li>
        ))}
      </ul>
    </div>
  );
}

Movie.propTypes = {
  id: PropTypes.number.isRequired,
  title: PropTypes.string.isRequired,
  coverImg: PropTypes.string.isRequired,
  summary: PropTypes.string,
  genres: PropTypes.arrayOf(PropTypes.string),
};

export default Movie;

'React' 카테고리의 다른 글

[React] TypeScript  (0) 2023.08.17
[React] styled-components  (0) 2023.08.16
[React 기초] 07. 예제 - Coin tracker  (0) 2023.08.10
[React 기초] 06. 예제 - Todo List  (0) 2023.08.10
[React 기초] 05. effects  (0) 2023.08.10

코인 파프리카 api

해당 주소를 fetch하여 api 요청하고,

then, 응답으로 온 response를 json으로 변환하고,

then, json으로 변환된 데이터를 console.log 해보기

위 과정은 처음 랜더링할 때 한 번만 하면 되므로 useEffect(funciton, []) 적용

useEffect(() => {
  fetch("https://api.coinpaprika.com/v1/tickers")
    .then((response) => response.json())
    .then((json) => console.log(json));
}, []);

위 코드는 async와 await을 사용한 코드로 사용할 수 있음

const getCoin = async () => {
  const response = await fetch(
    "https://api.coinpaprika.com/v1/tickers"
  );
  const json = await response.json();
  console.log(json)
};
useEffect(() => {
  getCoin();
}, []);

Coin tracker ver.1

현재 코인 시세를 리스트로 출력

import { useEffect, useState } from "react";

function App() {
  const [loading, setLoading] = useState(true);
  const [coins, setCoins] = useState([]);
  useEffect(() => {
    fetch("https://api.coinpaprika.com/v1/tickers")
      .then((response) => response.json())
      .then((json) => {
        setCoins(json);
        setLoading(false);
      });
  }, []);
  const today = new Date();
  return (
    <div>
      <h1>The Coins! - {today.toLocaleString()}</h1>
      {loading ? <strong>Loading...</strong> : null}
      <ul>
        {coins.map((item) => (
          <li key={item.id}>
            {item.name}({item.symbol}) : $ {item.quotes.USD.price}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

Coin tracker ver.2

현재 가지고 있는 달러를 입력 받고, 선택한 코인 종류에 따라서 환전량을 보여줄 수 있도록 개선

select를 선택할 때마다 state 값을 변경

option에 있는 text를 가져올 수 있는 방법을 찾지 못해 value에 시세, 심볼을 ","로 이어서 받은 뒤, split(",")을 사용하여 분리하는 방법을 채택

import { useEffect, useState } from "react";

function App() {
  const [loading, setLoading] = useState(true);
  const [coins, setCoins] = useState([]);
  const [cash, setCash] = useState(0);
  const [currentCoin, setCurrentCoin] = useState(false);
  const onChangeCash = (event) => {
    setCash(event.target.value);
  };
  const onChangeCoin = (event) => {
    setCurrentCoin(event.target.value);
  };
  useEffect(() => {
    fetch("https://api.coinpaprika.com/v1/tickers")
      .then((response) => response.json())
      .then((json) => {
        setCoins(json);
        setLoading(false);
      });
  }, []);
  const today = new Date();
  return (
    <div>
      <h1>The Coins! - {today.toLocaleString()} 기준</h1>
      {loading ? (
        <strong>Loading...</strong>
      ) : (
        <div>
          <label htmlFor="cash">I have $</label>
          <input
            id="cash"
            value={cash}
            onChange={onChangeCash}
            type="number"
          ></input>
          <br />
          <label htmlFor="coin">Exchange to </label>
          <select id="coin" onChange={onChangeCoin}>
            {coins.map((item) => (
              <option
                key={item.id}
                value={[item.quotes.USD.price, item.symbol]}
              >
                {item.name}({item.symbol})
              </option>
            ))}
          </select>
          <br />
          {currentCoin ? (
            <strong>
              You can buy{" "}
              <span>{cash / Number(currentCoin.split(",")[0])}</span>{" "}
              <span>{currentCoin.split(",")[1]}</span>
            </strong>
          ) : (
            <strong>Please select coin</strong>
          )}
        </div>
      )}
    </div>
  );
}

export default App;

'React' 카테고리의 다른 글

[React] styled-components  (0) 2023.08.16
[React 기초] 08. 예제 - Movie info  (0) 2023.08.10
[React 기초] 06. 예제 - Todo List  (0) 2023.08.10
[React 기초] 05. effects  (0) 2023.08.10
[React 기초] 04. create-react-app  (0) 2023.08.10

JS 문법 

전개구문

 

전개 구문 - JavaScript | MDN

전개 구문을 사용하면 배열이나 문자열과 같이 반복 가능한 문자를 0개 이상의 인수 (함수로 호출할 경우) 또는 요소 (배열 리터럴의 경우)로 확장하여, 0개 이상의 키-값의 쌍으로 객체로 확장시

developer.mozilla.org

전개 구문을 사용하여 기존 배열에서 새로운 배열을 쉽게 추가할 수 있음

const array = [1, 2, 3, 4];
const newElement = 5;
const newArray = [...array, newElement];

map

map 함수를 사용하여, Array에 있는 요소를 react element로 출력할 수 있음

{
  toDos.map((item, index) => <li key={index}>{item}</li>);
}

Todo List

import { useEffect, useState } from "react";

function App() {
  const [toDo, setToDo] = useState("");
  const [toDos, setToDos] = useState([]);
  const onChange = (event) => {
    setToDo(event.target.value);
  };
  const onSubmit = (event) => {
    event.preventDefault();
    if (toDo === "") {
      return;
    }
    // 아래와 같이 state를 직접적으로 수정하지 않는다
    // toDos.push()
    // state는 함수를 사용하여 값을 수정
    setToDos((currentArray) => [toDo, ...currentArray]);
    setToDo("");
  };
  return (
    <div>
      <h1>My To Dos ({toDos.length})</h1>
      <form onSubmit={onSubmit}>
        <input
          onChange={onChange}
          value={toDo}
          type="text"
          placeholder="write what you have to..."
        ></input>
        <button>Add to Do</button>
      </form>
      <hr />
      {toDos.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </div>
  );
}

export default App;

'React' 카테고리의 다른 글

[React 기초] 08. 예제 - Movie info  (0) 2023.08.10
[React 기초] 07. 예제 - Coin tracker  (0) 2023.08.10
[React 기초] 05. effects  (0) 2023.08.10
[React 기초] 04. create-react-app  (0) 2023.08.10
[React 기초] 03. Props  (0) 2023.08.02

아래 코드에서 state가 변경될 때마다, console.log가 실행된다.

첫 번째 render할 때만 실행하고 싶다면?

import { useState } from "react";

function App() {
  const [counter, setValue] = useState(0);
  const onClick = () => setValue((prev) => prev + 1);
  console.log("call an API");
  return (
    <div>
      <h1>{counter}</h1>
      <button onClick={onClick}>Click me!</button>
    </div>
  );
}

export default App;

 

useEffect

  • 컴포넌트 안에 딱 한 번만 실행하고 싶은 코드가 있을 경우
  • 컴포넌트 안에 특정 데이터가 변화할 때만 실행해야하는 경우
  • dependencies: React가 지켜봐야하는 것
    • dependencies가 [] (빈 리스트)이면 지켜봐야할 것이 없다 = 한 번만 실행
import { useState, useEffect } from "react";

function App() {
  const [counter, setValue] = useState(0);
  const onClick = () => setValue((prev) => prev + 1);
  console.log("I run all the time");
  const iRunOnlyOnce = () => {
    console.log("I run only once");
  };
  useEffect(iRunOnlyOnce, []);
  return (
    <div>
      <h1>{counter}</h1>
      <button onClick={onClick}>Click me!</button>
    </div>
  );
}

export default App;

예제 - 검색창 작동 방식

import { useState, useEffect } from "react";

function App() {
  const [counter, setValue] = useState(0);
  const [keyword, setKeyword] = useState("");
  const onClick = () => setValue((prev) => prev + 1);
  const onChange = (event) => {
    setKeyword(event.target.value);
  };
  console.log("I run all the time");
  useEffect(() => {
    console.log("i run only once");
  }, []);
  console.log("SEARCH FOR", keyword);
  return (
    <div>
      <input
        value={keyword}
        onChange={onChange}
        type="text"
        placeholder="Search here"
      ></input>
      <h1>{counter}</h1>
      <button onClick={onClick}>Click me!</button>
    </div>
  );
}

export default App;

Problem

  • input에 있는 단어가 변경될 때마다 SEARCH FOR 실행
  • 검색과 관련 없는 button을 onClick시에도 SEARCH FOR 실행

Scratch

useEffect를 사용, 두 번째 인자에 keyword 변수 사용
useEffect(() => {
  console.log("SEARCH FOR", keyword);
}, [keyword]);
import { useState, useEffect } from "react";

function App() {
  const [counter, setValue] = useState(0);
  const [keyword, setKeyword] = useState("");
  const onClick = () => setValue((prev) => prev + 1);
  const onChange = (event) => {
    setKeyword(event.target.value);
  };
  console.log("I run all the time");
  useEffect(() => {
    console.log("i run only once");
  }, []);
  useEffect(() => {
    console.log("SEARCH FOR", keyword);
  }, [keyword]);
  return (
    <div>
      <input
        value={keyword}
        onChange={onChange}
        type="text"
        placeholder="Search here"
      ></input>
      <h1>{counter}</h1>
      <button onClick={onClick}>Click me!</button>
    </div>
  );
}

export default App;

Scratch 2

처음에 SEARCH FOR가 실행되는 것을 막으려면 if문으로 조건을 추가해주는  방법이 있다

useEffect(() => {
  if (keyword.length > 0) {
    console.log("SEARCH FOR", keyword);
  }
}, [keyword]);

예제 - useEffect 작동 확인

import { useState, useEffect } from "react";

function App() {
  const [counter, setValue] = useState(0);
  const [keyword, setKeyword] = useState("");
  const onClick = () => setValue((prev) => prev + 1);
  const onChange = (event) => {
    setKeyword(event.target.value);
  };
  console.log("I run all the time");
  useEffect(() => {
    console.log("I run only once");
  }, []);
  useEffect(() => {
    console.log("I run when 'keyword' changes.");
  }, [keyword]);
  useEffect(() => {
    console.log("I run when 'counter' changes.");
  }, [counter]);
  useEffect(() => {
    console.log("I run when 'keyword' and 'counter' changes.");
  }, [keyword, counter]);
  return (
    <div>
      <input
        value={keyword}
        onChange={onChange}
        type="text"
        placeholder="Search here"
      ></input>
      <h1>{counter}</h1>
      <button onClick={onClick}>Click me!</button>
    </div>
  );
}

export default App;

cleanup function

react는 컴포넌트를 destroy할 때도 함수를 사용 가능

아래 코드에서 Hello 컴포넌트가 사라질 때, useEffect 안의 함수의 return문이 실행

- Nico는 거의 사용하지 않는다고 했음

import { useEffect, useState } from "react";

function Hello() {
  useEffect(() => {
    console.log("created!");
    return () => console.log("detroyed!");
  }, []);
  return <h1>Hello</h1>;
}

function App() {
  const [showing, setShowing] = useState(false);
  const onClick = () => setShowing((prev) => !prev);
  return (
    <div>
      {showing ? <Hello /> : null}
      <button onClick={onClick}>{showing ? "Hide" : "Show"}</button>
    </div>
  );
}

export default App;

Component Life Cycle

위의 useEffect는 React에서 Hooks라는 개념

이 이전에는 React를 class 형태로 사용했었다. 

  • Mounting
    • constructor: render 되기 전에 호출
    • componentDidMount: component가 처음 rendering되었을 때 실행, useEffect의 dependencies가 빈 리스트일 경우와 동일
  • Updating
    • componentDidUpdate: state가 변경점이 생겨, 새로 rendering된 경우에 실행
  • Unmounting
    • componentWillUnmount: component가 사라질 경우에 호출 (페이지 이동 시)
import React from "react";

class App extends React.Component {
  componentDidMount() {
    console.log("component rendered");
  }
  render() {
    console.log("I am rendering");
    return;
  }
}

export default App;
// Result
// I am rendering
// component rendered

 

'React' 카테고리의 다른 글

[React 기초] 07. 예제 - Coin tracker  (0) 2023.08.10
[React 기초] 06. 예제 - Todo List  (0) 2023.08.10
[React 기초] 04. create-react-app  (0) 2023.08.10
[React 기초] 03. Props  (0) 2023.08.02
[React 기초] 02. State  (0) 2023.08.02

https://create-react-app.dev/

 

Create React App

Set up a modern web app by running one command.

create-react-app.dev

실행하기 위해서는  NodeJS가 필요함

npx는 패키지 실행을 위한 도구로, 패키지의 최신버전 파일을 불러와 설치하여 실행시키고 실행된 이후에 해당 패키지를 제거하는 방식

npx create-react-app my-app
  • my-app으로 파일 생성되고 안에 패키지가 설치됨
  • package에 기본적인 명령어가 설정되어 있음
  • 개발용 server를 열게됨

package.json에서 기본적으로 설정된 명령어를 scripts에서 확인 가능

살펴보기

index.js에서 App을 랜더링하고 있음
css를 import 할 수 있음
// index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./style.css";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
App.js에서 컴포넌트를 불러옴
// App.js
import Button from "./Button";

function App() {
  return (
    <div>
      <h1>Wecome back!</h1>
      <Button text={"continue"} />
    </div>
  );
}

export default App;
각 컴포넌트를 사용할 시 export 해줘야 함
// Button.js
import PropTypes from "prop-types";

function Button({ text }) {
  return <button>{text}</button>;
}
Button.propTypes = {
  text: PropTypes.string.isRequired,
};

export default Button;

CSS 적용하기

1. 한 style.css 파일에 모든 내용 저장

2. 각각의 컴포넌트마다 style 지정

3. Divide & Conquer

 

각각의 컴포넌트마다 style 지정
import PropTypes from "prop-types";

function Button({ text }) {
  return (
    <button
      style={{
        color: "white",
        backgroundColor: "tomato",
      }}
    >
      {text}
    </button>
  );
}
Button.propTypes = {
  text: PropTypes.string.isRequired,
};

export default Button;
Divide & Conquer
/* Button.module.css */
.btn {
  color: white;
  background-color: tomato;
}
// Button.js
import PropTypes from "prop-types";
import styles from "./Button.module.css";

function Button({ text }) {
  return <button className={styles.btn}>{text}</button>;
}
Button.propTypes = {
  text: PropTypes.string.isRequired,
};

export default Button;

이렇게 할 경우, 실제 브라우저에서 해당 페이지가 랜더링 될 때, class 이름은 btn-xxxx 이런 식으로 무작위로 들어감.

다른 곳에서 같은 class 이름을 사용하여도 겹치지 않음!

위에서 JSX를 사용할 때, JS에서 class는 예약어라서 className으로 속성 값 부여

마찬가지로 for도 JSX 속성값으로 줄 경우, htmlFor로 사용

'React' 카테고리의 다른 글

[React 기초] 06. 예제 - Todo List  (0) 2023.08.10
[React 기초] 05. effects  (0) 2023.08.10
[React 기초] 03. Props  (0) 2023.08.02
[React 기초] 02. State  (0) 2023.08.02
[React 기초] 01. Basics of React JS  (0) 2023.08.02

Props

  • 부모 컴포넌트로부터 자식 컴포넌트에 데이터를 보낼 수 있는 방법
  • 스타일을 저장한 컴포넌트를 재사용할 수 있다면?
  • 함수형태로 만든 컴포넌트에 인자 props를 전달 가능
  • props는 object형태로 받아서 props.property 형태로 인자 전달 가능
function Btn(props) {
  console.log(props);
  return (
    <button
      style={{
        backgroundColor: "tomato",
        color: "white",
        padding: "10px 20px",
        border: 0,
        borderRadius: "10px",
      }}
    >
      {props.potato}
    </button>
  );
}
function ConfirmBtn() {
  return <button>Confirm</button>;
}
function App() {
  return (
    <div>
      <Btn potato="Save Changes" />
      <Btn potato="Continue" />
    </div>
  );
}
const root = document.getElementById("root");
ReactDOM.render(<App />, root);
JS 문법을 이용해서 props 부분 짧게 쓰는 방법
function Btn({ potato }) {
  return <button> {potato} </button>;
}
응용
function Btn({text, big})
  return (
    <button
    style={{
      fontSize: big ? 18: 10,
      }}
    >
      {text}
    </button>;
  )
function App() {
  return (
    <div>
      <Btn text="Save Changes" big={true}/>
      <Btn text="Continue" big={false}/>
    </div>
  );
}
내가 직접 만든 컴포넌트에 준 props 값은 return 값에 자동으로 들어가는 것이 아님!
내가 만든 Btn에 onClick 이벤트를 추가하려면, 실제로는 props.onClick 속성 값으로 들어감
따라서 실제 return 값에 있는 button 태그에 onClick 이벤트를 추가하고
props.onClick 값을 추가해줘야 함
function Btn({ text, onClick }) {
  return (
    <button
      onClick={onClick}
      style={{
        backgroundColor: "tomato",
        color: "white",
        padding: "10px 20px",
        border: 0,
        borderRadius: "10px",
      }}
    >
      {text}
    </button>
  );
}
function App() {
  const [value, setValue] = React.useState("Save Changes");
  const changeValue = () => setValue("Revert Changes");
  return (
    <div>
      <Btn text={value} onClick={changeValue} />
      <Btn text="Continue" />
    </div>
  );
}
const root = document.getElementById("root");
ReactDOM.render(<App />, root);

memo

바뀌는 것만 render 하기
전체를 re-render하는 것이 아니라 컴포넌트의 변경점이 있는 컴포넌트만 re-render할 수 있다
const MemorizedBtn = React.memo(Btn);
function Btn({ text, onClick }) {
  console.log(`${text} was rendered`);
  return (
    <button
      onClick={onClick}
      style={{
        backgroundColor: "tomato",
        color: "white",
        padding: "10px 20px",
        border: 0,
        borderRadius: "10px",
      }}
    >
      {text}
    </button>
  );
}
const MemorizedBtn = React.memo(Btn);
function App() {
  const [value, setValue] = React.useState("Save Changes");
  const changeValue = () => setValue("Revert Changes");
  return (
    <div>
      <MemorizedBtn text={value} onClick={changeValue} />
      <MemorizedBtn text="Continue" />
    </div>
  );
}
const root = document.getElementById("root");
ReactDOM.render(<App />, root);

PropTypes

Proptypes package는 어떤 타입의 props을 받고 있는지 체크할 수 있다
전체적인 버전에 따라서 작동 여부가 달라지는 것 같다

아래 버전으로 했을 때 정상 작동되는 것을 확인했음

<script src="https://unpkg.com/react@17.0.2/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://unpkg.com/prop-types@15.7.2/prop-types.js"></script>
function Btn({ text, fontSize = 16 }) {
  return (
    <button
      style={{
        backgroundColor: "tomato",
        color: "white",
        padding: "10px 20px",
        border: 0,
        borderRadius: "10px",
        fontSize,
      }}
    >
      {text}
    </button>
  );
}
Btn.propTypes = {
  text: PropTypes.string.isRequired,
  fontSize: PropTypes.number,
};
function App() {
  return (
    <div>
      <Btn text="Save Changes" fontSize={18} />
      <Btn text={18} fontSize={"continue"} />
    </div>
  );
}
const root = document.getElementById("root");
ReactDOM.render(<App />, root);

'React' 카테고리의 다른 글

[React 기초] 06. 예제 - Todo List  (0) 2023.08.10
[React 기초] 05. effects  (0) 2023.08.10
[React 기초] 04. create-react-app  (0) 2023.08.10
[React 기초] 02. State  (0) 2023.08.02
[React 기초] 01. Basics of React JS  (0) 2023.08.02

State

  • state: 데이터가 저장되는 곳
  • 아래 예시에서 counter의 숫자 부분을 state로 대체 가능
  • 버튼을 누를 때 전체가 render되게 됨
  • 이렇게 구현해도 개발자 도구로 버튼을 누를 때마다 바뀌는 부분을 확인하면, 해당 부분 숫자만 바뀌는 것을 확인할 수있음 - react 짱
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Begin React</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.22.9/babel.min.js"></script>
  <script type="text/babel">
    const root = document.getElementById("root");
    let counter = 0;
    function countUp() {
      counter = counter + 1;
      render();
    }
    function render() {
      ReactDOM.render(<Container />, root);
    }
    const Container = () => (
      <div>
        <h3>Total clicks: {counter}</h3>
        <button onClick={countUp}>Click me</button>
      </div>
    );
    ReactDOM.render(<Container />, root);
  </script>
</html>
조금 더 rendering을 편하게 하는 방법은 없을까?

useState

[추가팁] JS 문법 
const food = ["tomato", "potato"];
const [myFavFood, mySecondFavFood] = food;
// same as myFavFood = food[0];, mySecondFavFood = food[1];
// myFavFood
// 'tomato'
useState를 사용해서 바뀐 부분과 함께 render
const [counter, setCounter] = React.useState(0);
const onClick = () => {
// modifier function은 값을 변화시킨 다음에 rerender 시켜줌
setCounter(counter + 1);
};
modifier function alpha; 변수를 통한 직접적인 값 변화보다는 함수로 처리하는 것이 더 안전
const [counter, setCounter] = React.useState(0);
const onClick = () => {
  // modifier function은 값을 변화시킨 다음에 rerender 시켜줌
  // setCounter(counter + 1);
  setCounter((current) => current + 1);
};

최종 코드

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Begin React</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.22.9/babel.min.js"></script>
  <script type="text/babel">
    const root = document.getElementById("root");
    function App() {
      const [counter, setCounter] = React.useState(0);
      const onClick = () => {
        // modifier function은 값을 변화시킨 다음에 rerender 시켜줌
        // setCounter(counter + 1);
        setCounter((current) => current + 1);
      };
      return (
        <div>
          <h3>Total clicks: {counter}</h3>
          <button onClick={onClick}>Click me</button>
        </div>
      );
    }
    ReactDOM.render(<App />, root);
  </script>
</html>

유의 사항

JSX를 사용할 때는 JSX에 맞는 프로퍼티를 사용해야 함

  • class -> className
  • for -> htmlFor

React는 SyntheticBaseEvent를 발생시킴, 그 안에 nativeEvent 속성 값이 있음


분을 시간으로 환산해주는 계산기 만들어보기

  • minutes에 값을 입력하면 hour로 변환
  • input에 값을 입력 시, onChange 이벤트가 발생
  • onChange 이벤트는 setMinutes()를 작동
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Begin React</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.22.9/babel.min.js"></script>
  <script type="text/babel">
    const root = document.getElementById("root");
    function App() {
      const [minutes, setMinutes] = React.useState(0);
      const onChange = (event) => {
        // console.log(event.target.value);
        setMinutes(event.target.value);
      };
      const reset = () => setMinutes(0);
      return (
        <div>
          <h1>Super Converter</h1>
          <label htmlFor="minutes">Minutes</label>
          <input
            id="minutes"
            placeholder="Minute"
            type="number"
            value={minutes}
            onChange={onChange}
          />
          <br></br>
          <label htmlFor="hours">Hours</label>
          <input
            id="hours"
            placeholder="Hours"
            type="number"
            disabled
            value={Math.round(minutes / 60)}
          />
          <br></br>
          <button onClick={reset}>Reset</button>
        </div>
      );
    }
    ReactDOM.render(<App />, root);
  </script>
</html>

시간도 분으로 계산할 수 있도록 개선해보기

  • hour input 태그에도 onChange 함수 적용
  • onFlip 이라는 변수로 현재 flipped된 상태인지 아닌지 구분
  • tenary syntax로 onFlip 변수의 상태마다 value 값이 다르게 출력될 수 있도록 함
  • tenary syntax로 onFlip 변수의 상태마다 disabled가 적용될 수 있도록 함
  • tenary syntax로 onFlip 변수의 상태마다 button의 value 값이 적용될 수 있도록 함
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Begin React</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.22.9/babel.min.js"></script>
  <script type="text/babel">
    const root = document.getElementById("root");
    function App() {
      const [amount, setAmount] = React.useState(0);
      const [flipped, setFlipped] = React.useState(false);
      const onChange = (event) => {
        // console.log(event.target.value);
        setAmount(event.target.value);
      };
      const reset = () => setAmount(0);
      // const onFlip = () => setflip(!flipped);
      const onFlip = () => {
        setFlipped((current) => !current);
        reset();
      };
      return (
        <div>
          <h1>Super Converter</h1>
          <label htmlFor="minutes">Minutes</label>
          <input
            id="minutes"
            placeholder="Minute"
            type="number"
            onChange={onChange}
            value={flipped ? amount * 60 : amount}
            disabled={flipped}
          />
          <br></br>
          <label htmlFor="hours">Hours</label>
          <input
            id="hours"
            placeholder="Hours"
            type="number"
            onChange={onChange}
            value={flipped ? amount : Math.round(amount / 60)}
            disabled={flipped === false}
          />
          <br></br>
          <button onClick={reset}>Reset</button>
          <button onClick={onFlip}>
            {flipped ? "Minutes to Hours" : "Hours to Minutes"}
          </button>
        </div>
      );
    }
    ReactDOM.render(<App />, root);
  </script>
</html>

Class 사용 예시

import React from "react";

class App extends React.Component {
  state = {
    count: 0,
  };
  plus = () => {
    this.setState((current) => ({ count: current.count + 1 }));
  };
  minus = () => {
    this.setState((current) => ({ count: current.count - 1 }));
  };
  render() {
    return (
      <div>
        <h1>The number is {this.state.count}</h1>
        <button onClick={this.plus}>plus</button>
        <button onClick={this.minus}>minus</button>
      </div>
    );
  }
}

export default App;

'React' 카테고리의 다른 글

[React 기초] 06. 예제 - Todo List  (0) 2023.08.10
[React 기초] 05. effects  (0) 2023.08.10
[React 기초] 04. create-react-app  (0) 2023.08.10
[React 기초] 03. Props  (0) 2023.08.02
[React 기초] 01. Basics of React JS  (0) 2023.08.02

+ Recent posts