[230512] 스타일 적용하기 | 합성 컴포넌트 | React Router

윤지수·2023년 5월 12일
1
post-thumbnail

⚛️ 스타일 적용하기

💡 스타일을 적용하는 방식은 크게 CSS, CSS-in-CSS(Sass 등), CSS-in-JS(styled-components 등)으로 나뉜다.

module.css

  • index.js에 index.css를 import하게 되면 전역으로 CSS가 적용된다.
  • App.js에 App.css를 import하게 되면 App 컴포넌트에 들어있는 모든 컴포- 넌트에 CSS가 적용된다.
  • 부모 컴포넌트에서 import한 CSS는 자식까지 자동으로 적용되고, 자식 컴포넌트에 새로 CSS를 적용하게 되면 부모 컴포넌트의 스타일은 자식 컴포넌트에 import된 스타일이 덮어쓰게 된다.

리액트에서도 기본적으로 CSS 선택자 우선순위가 적용되며, 컴포넌트에 스타일을 사용하는게 다른 컴포넌트의 스타일에도 영향을 미친다. 클래스를 이용해 우선순위를 높이려는 노력도 결국 컴포넌트끼리 클래스 이름이 중복되면 소용없어지게 된다. 특히나 컴포넌트로 분리하여 작업을 하는 리액트의 특성상 클래스 이름이 겹칠 위험성은 상당히 크다.

CSS 파일 이름에 .css를 module.css로 바꿔주면 해결 가능!

module을 파일명에 추가해준 뒤 className={styles.클래스명}으로 해준다면 아래처럼 자동으로 클래스명이 겹치지 않도록 처리해준다. 클래스 이름이 컴포넌트별로 겹치지 않도록 고민할 필요가 없다🙅🏻‍♀️! 그래서 module.css를 사용할 때는 클래스를 많이 쓴다.
module.css

💡 module.css를 사용할 때 주의할 점은 적용하고자 하는 컴포넌트 이름과 반드시 일치시켜줘야 한다.
ex) Detail.jsx 파일에만 적용해주고 싶은 module.css의 파일명은 Detail.module.css

styled-components

npm install styled-components
npm install styled-components@5

CSS-in-JS 관련하여 가장 인기 있는 리액트 라이브러리

  • 스타일 컴포넌트 모듈
  • 스타일된 컴포넌트를 만드는 것
  • CSS를 컴포넌트에 쉽게 추가해주는 방법
  • CSS 파일을 별도로 만드느냐 안만드느냐의 차이

👍🏻 컴포넌트 파일 보고, css 파일 보고 왔다갔다 할 필요없이 컴포넌트 파일만 봐도 파악 가능하다.
👍🏻 컴포넌트를 만들면서 동시에 스타일을 정해줄 수 있다. 스타일, HTML을 하나의 자바스크립트 파일 안에서 관리할 수 있다.

글로벌 스타일

글로벌(전역) 스타일은 모든 컴포넌트에서 공통으로 적용되는 스타일
웹 페이지의 배경색이나 글꼴 설정 등은 모든 컴포넌트에서 공통으로 사용되는 스타일이므로 전역 스타일로 관리하는 것이 효율적이다.

styled-components가 제공하는 crateGlobalStyle() 함수를 사용하여 글로벌 스타일 컴포넌트를 생성하고 App.js(현재 최상위 컴포넌트) 상단에 추가해주면 모든 하위 컴포넌트에 스타일이 적용된다.

import { createGlobalStyle } from "styled-components";
import Example from "./Components/Example";

const GlobalStyle = createGlobalStyle`
  span {
    color: red;
    font-size: 12px;
  }
`;

function App() {
  return (
    <>
      <GlobalStyle/>
      <h1>hello world 1</h1>
      <span>hello world 2</span>
      <Example/>
    </>
  );
}

export default App;

글로벌 스타일은 다른 스타일보다 나중에 로딩이 되어 같은 가중치라면 기본적으로 우선순위가 높다.

tagged tamplate literal(ES6 문법)
문자열 리터럴을 함수 호출의 인자로 전달하여 해당 문자열을 가공하는 기능
함수는 문자열 리터럴을 ${표현식}을 기준으로 나누어 각 부분을 배열의 요소로 전달받아, 이를 이용해 최종적인 문자열을 반환한다.
첫 번째 인수: 문자열 값의 배열, 나머지 인수: 표현식

const name = "윤말랑";
const age = 10;
function 인사(문구, 이름, 나이) {
  console.log(문구, 이름, 나이);	
  // ['이름은 ', '이고 나이는 ', '입니다.', raw: Array(3)] '윤말랑' 20
  return `${문구[0]}${이름}${문구[1]}${나이 + 나이}${문구[2]}`;
}
const 인사문구 = 인사`이름은 ${name}이고 나이는 ${age + age}입니다.`;
console.log(인사문구);	// 이름은 윤말랑이고 나이는 40입니다.

reset css

npm i styled-reset
npm i styled-normalize

styled-reset/styled-normalize라는 패키지를 다운받은 뒤 styled-reset/styled-normalize이 제공하는 reset/normalize를 글로벌 스타일에 적용한다.

import { createGlobalStyle } from "styled-components";
import Example from "./Components/Example";
import reset from "styled-reset";
import normalize from "styled-normalize";

const GlobalStyle = createGlobalStyle`
  ${reset}
  ${normalize}

  span {
	color: red;
	font-size: 12px;
  }

  a {
	text-decoration : none;
	color : inherit;
  }

  button {
	border : none;
	cursor : pointer;
  }

  * {
	box-sizing: border-box;
  }
`;

function App() {
  return (
    <>
      <GlobalStyle/>
      <h1>hello world 1</h1>
      <span>hello world 2</span>
      <Example/>
    </>
  );
}

export default App;

단독으로 사용할 수도 있다.

import { Reset } from 'styled-reset'

function App() {
  <>
    <Reset />
    <h1>hello world 1</h1>
    <span>hello world 2</span>
  <>
)

컴포넌트 스타일

컴포넌트 안에서 개별적으로 적용되는 스타일

styled-components가 제공하는 styled를 사용하여 스타일 컴포넌트를 생성한다. 스타일 컴포넌트가 클래스명을 난수화시켜주기 때문에 클래스 이름이 겹칠 일이 없어서 클래스 이름을 고민할 필요가 없다.

const 컴포넌트명 = styled.태그명`
  background-color : red;
`
  • props에 따른 조건부 스타일 적용
    ${} 안에 코드를 입력한다.
import React from "react";
import styled from "styled-components";

const ContentDiv = styled.div`
  margin: 40px;
`;

const ContentH2 = styled.h2`
  color: ${(props) => (props.name === 'hello'? 'red' : 'black')};
  width: 200px;
  margin: 0 auto;
  text-align: center;
`;


const App = () => {
  return (
    <ContentDiv>
      <ContentH2 name="hello">Q&A</ContentH2>
      <p>
    	Lorem ipsum dolor sit amet consectetur adipisicing elit. Eos excepturi corrupti quo blanditiis! Adipisci amet corporis ipsum odio minima aliquid quisquam! Dignissimos natus laborum qui veritatis quaerat eaque!  Nemo, ullam.
      </p>
    </ContentDiv>
    );
};

export default App;
  • 확장해서 사용 가능
import React from "react";
import styled from "styled-components";

const ContentOne = styled.div`
  margin: 40px;
`;

const ContentTwo = styled.div`
  color:red;
`;

const ContentThree = styled(ContentTwo)`
  border: 1px solid black;
`

const App = () => {
  return (
    <div>
      <ContentOne>hello world</ContentOne>
      <ContentTwo>hello world</ContentTwo>
      <ContentThree>hello world</ContentThree>
    </div>
  );
}

export default App;
  • Sass 문법 사용 가능
const UniAfter = styled.div`
  &::after {
	content: "!!";
  }
`
  • CSS 확장 가능
import React from "react";
import styled, { css } from 'styled-components'

const One = css`
  color: red;
`;

const Two = css`
  border: 1px solid black;
`;

const Three = styled.div`
  ${One}
  ${Two}
`

const App = () => {
  return (
    <Three>Lorem ipsum dolor</Three>
  );
};
  
export default App;

⚛️ 합성 컴포넌트

컴포넌트 합성
작은 컴포넌트와 작은 컴포넌트를 결합하여 더 큰 컴포넌트를 만드는 것

합성 컴포넌트를 사용하면 여러 컴포넌트에서 재사용 가능한 작은 컴포넌트를 만들 수 있으며, 컴포넌트의 구조가 단순해져서 가독성과 유지보수성이 높아진다.

공통된 부분을 새로운 컴포넌트로 만들고 이 컴포넌트가 필요한 컴포넌트로 가져와서 사용한다.
ex) 모달을 만들 때마다 동일한 코드를 계속 작성하는 것이 아니라 겹치는 부분을 컴포넌트로 만들어서 재사용

컴포넌트를 사용하는 부분에서 다른 컴포넌트를 자식으로 끼워 넣으면 자동으로 컴포넌트 선언부의 props에 children이라는 키 값으로 들어간다.
자식으로 들어올 컴포넌트 형태의 경우의 수가 많을 경우에 부모 컴포넌트는 prop.children을 사용하여 자식 엘리먼트를 받는다.

⚛️ React Router

URL에 따라서 보여지는 페이지가 바뀌게 하는 기능을 구현할 수 있다.

라우팅
내가 네이버를 주소창에 치면 네이버가 어딘가 저장되어 있는 곳으로의 적절한 길을 찾아준다

npm install react-router-dom
import { BrowserRouter, Routes, Route, Link, useLocation, useParams, Outlet } from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <Link to="/"> home </Link>
      <Link to="/one"> one </Link>
      <Link to="/two"> two </Link>
      <Link to="/three"> three </Link>
      <Link to="/blog/1"> four_1 </Link>
      <Link to="/blog/2"> four_2 </Link>
      <Link to="/blog/3"> four_3 </Link>
      <Routes>
        <Route
          path="/"
          element={<Index />}
        />
        {/* props 전달 */}
        <Route
          path="/one"
          element={<One name="licat" />}
        />
        <Route
          path="/two"
          element={<Two />}
        />
        {/* Router 중첩 */}
        <Route
          path="/three/*"
          element={<Outlet />}
        >
          <Route
            path=""
            element={<MallangIndex />}
          />
          <Route
            path="hojunone/"
            element={<MallangOne />}
          />
          <Route
            path="hojuntwo/"
            element={<MallangTwo />}
          />
        </Route>
        {/* 파라미터 설정 */}
        <Route
          path="/blog/:id"
          element={<Blog />}
        />
      </Routes>
    </BrowserRouter>
  );
}

function Index() {
  return <h1>hello world0</h1>;
}

function One({ name }) {
  return <h1>{name} world1</h1>;
}

function Two() {
  return <h1>hello world2</h1>;
}

function Three() {
  return <h1>hello world3</h1>;
}

function Blog() {
  const location = useLocation(); // URL 정보
  console.log(location);	// {pathname: '/blog/3', search: '', hash: '', state: null, key: '5hnzc69s'}

  const path = location.pathname.split("/")[2];
  console.log(path);	// 3

  // 파라미터만 잘라내주는 훅
  const {id} = useParams();
  console.log(id);	// 3
  
  // 같은 컴포넌트를 불러와도 다른 모습으로 변경하는 것이 가능하다
  return <h1>hello Blog this is blog page {id}</h1>;
}

function MallangIndex() {
  const location = useLocation();
  console.log(location);
  return <h1>hello Mallang index</h1>;
}

function MallangOne() {
  const location = useLocation();
  console.log(location);
  return <h1>hello Mallang 1</h1>;
}

function MallangTwo() {
  const location = useLocation();
  console.log(location);
  return <h1>hello Mallang 2</h1>;
}

export default App;

BrowserRouter 컴포넌트
UI와 URL을 연결한다. 마치 SSR과 같이 URL을 사용할 수 있게 한다.
"얘네가 브라우저 안에서 보여주는 라우터야"

Route 컴포넌트
현재 URL과 매칭된 UI를 렌더링하는 역할을 한다.
"이 주소로 왔을 때 이 컴포넌트를 보여줄게"

Routes 컴포넌트
URL이 변경되면 <Routes>는 모든 자식 <Route>를 살펴보고 가장 알맞는 것을 매칭한다.

검색엔진 최적화(SEO)
검색엔진 봇들이 돌아다니면서 웹 페이지를 찾아다니는데 URL 주소를 통해 웹 페이지를 파악하고 내부 서버로 페이지에 대한 정보를 전송한다. 정보를 바탕으로 검색엔진에 검색하면 보여줄 페이지에 대한 색인 작업이 이루어진다.
검색엔진 로봇이 사이트를 탐색하는데 URL 주소가 필수적이다. SPA라서 URL 주소가 한 개면 검색엔진 로봇이 탐색할게 없다고 생각하고 페이지를 분석하지 못한다. 그러면 서버에 페이지에 대한 정보를 전달하지 못하고 결국 검색에서 안 나오게 된다. 따라서 라우팅 처리를 안해주면 검색엔진 최적화에 치명적이다.

💡 라우팅 처리를 통해 리액트로 만들어도 검색엔진 최적화에 도움이 된다.

🔍 BrowserRouter는 페이지를 라우팅할 때 실제로 해당 URL에 HTML 파일이 존재하는 것은 아니지만 URL을 통해 마치 서버의 폴더를 이동하는 것처럼 보여준다. 이는 SPA임에도 불구하고 마치 SSR처럼 보여지기 때문에 검색엔진에서 로봇으로 웹사이트를 탐색할 때 크롤링이 가능하다.

🔍 HashRouter는 URL 주소에 #값을 넣어 표시한다. 주소 상의 #은 fragment identifier라 불리며 컨텐츠 안에서의 특정한 위치를 나타낸다.(ex. 내부링크) 이는 서버의 폴더구조를 이동하는 것과는 전혀 다른 모습이기 때문에 로봇을 통한 크롤링이 불가능해지고 결국 검색엔진 최적화에 악영향을 미친다.

Link 컴포넌트
클릭하면 애플리케이션 내에서 새로운 경로로 이동하는 링크를 생성한다.
Link 작성하는 방법: to 속성에는 접근할 경로가 들어간다.

// 문자열
<Link to="/courses?sort=name&sorting=asc" />

// 객체
<Link
  to={{
    pathname: "/courses",
    search: "?sort=name&sorting=asc",
    hash: "#the-hash",
    state: { fromDashboard: true }
  }}
/>

객체로 넣어줄 경우 pathname, search, hash, state 프로퍼티의 값들을 넣어서 객체 형태로 작성한다.

  • pathname: 이동할 경로를 나타내는 문자열
    ex) to={{ pathname: '/about'}}
  • search: 쿼리스트링을 나타내는 문자열
    ex) to={{ pathname: '/about', search: '?sort=name' }}
    이 경우 쿼리스트링 ?sort=name이 포함된 /about 경로로 이동한다.
  • hash: URL의 해시를 나타내는 문자열
    ex) to={{ pathname: '/about', hash: '#contact' }}
    이 경우 /about#contact 경로로 이동한다.
  • state: 이동할 경로와 함께 전달할 상태 정보
    ex) to={{ pathname: '/about', state: { fromDashboard: true } }}
    이 경우 /about 경로로 이동하면서 fromDashboard: true라는 상태 정보를 함께 전달한다.

💡 Link는 주소만 바꿀 뿐, 페이지를 새로 불러오진 않는다.(새로고침 X)

💡 a 태그를 쓰지않고 Link를 사용하는 이유
a 태그: href에 설정해준 경로 이동과 동시에 페이지를 새로 불러오기 때문에 페이지가 새로고침이 된다.
Link: HTML5 History API를 사용해서 브라우저의 주소를 바꿔주는 것이기 때문에 페이지를 불러오지 않고 DOM만 조작해서 페이지를 보여준다.

🔍 History API
브라우저의 세션 기록에 접근하는 API
ex) 앞으로 가기, 뒤로 가기

파라미터 설정
/:id
동적 라우팅을 위해 사용되는 URL 패턴
주소 뒤에 입력받은 값을 불러온다.

useLocation 훅은 현재 애플리케이션의 경로(location) 정보를 가져오는 데 사용된다. 이 훅을 사용하면 현재 URL의 경로, 쿼리스트링, 해시 등을 포함하는 객체를 반환하여 관련된 정보를 파악할 수 있다.
useParams 훅은 파라미터만 잘라내준다.

이 두 가지 정보를 결합하여 같은 컴포넌트를 불러와도 다른 모습으로 변경하는 것이 가능하다. 마치 컴포넌트에 props를 전달하여 변화를 주는 것과 같다.

Router 중첩
라우트 안에서 작성한 컴포넌트를 빼내주는 애
* : 뭐가 오든지(주소상관없이) element를 다 보여준다

0개의 댓글