styled-components

번외) react-router-dom의 Link를 styled-components로 꾸미기(feat.useLocation) (하지만 난 NavLink가 좋다)

윰윰로그 2021. 5. 17. 15:45

이 포스트는 Udemy 사이트에 올라 온 Tom Phillips의 "React styled-components v5 (2021 edition)" 강의를 듣고 정리한 것입니다.

 

🔗링크

https://www.udemy.com/course/react-styled-components/

 

React styled components v5 (2021 edition)

Ditch CSS stylesheets! Learn CSS in JS to quickly and cleanly style React components with the styled components library

www.udemy.com

 

 

 

 

📝 시작점

styled-componets가 아닌

일반적인 React의 컴포넌트 (react-router-dom의 Link 같은 컴포넌트 포함)들은

어떻게 styled-components로 스타일링할까?

 

 

 

 

 

 

🌟 예시) react-router-dom의 Link를 styled-components로 꾸미기

import styled from "styled-components";
import { Link } from "react-router-dom";

const StyledLink = styled(Link)`
	box-sizing: border-box;
	display: block;
	padding: 4px 8px;
	margin: 0 auto;
	text-align: center;
`;

export default function Header() {
	return (
    		<>
				<StyledLink to="/">Home</StyledLink>
				<StyledLink to="/login">LogIn</StyledLink>
		</>
	);
}

 

이렇게 styled(Link)``;를 해주면 Link의 모든 속성을 inherit하면서 새로운 속성을 더해줄 수 있다.

 

🔗 참고 

https://styled-components.com/docs/basics#extending-styles

 

styled-components: Basics

Get Started with styled-components basics.

styled-components.com

 

 

 

 

이렇게 코드를 작성하면 Link 컴포넌트에 작성한 모든 CSS 속성이 적용된다.

이렇게 간단할까?

그렇지 아니하다..........

원하는 스타일링이 다 적용되지만 이 방법은 조심해야할 사항이 하나 있다.

 

 

 

 

 

 

🙋‍♀️ 이 StyledLink에 'isActive'라는 props를 넣고 싶다고 가정해보자.

import styled from "styled-components";
import { Link } from "react-router-dom";

const StyledLink = styled(Link)`
	box-sizing: border-box;
	display: block;
	padding: 4px 8px;
	margin: 0 auto;
	text-align: center;

	font-weight: ${(props) => (props.isActive ? "bold" : "normal")};
`;

export default function Header() {
	return (
			<>
				<StyledLink to="/">Home</StyledLink>
				<StyledLink to="/login" isActive>
					LogIn
				</StyledLink>
			</>
	);
}

 

 

이렇게 코드를 작성하면 실제로 Login은 bold로 렌더링된다.

하지만 콘솔을 보면 

 

 

이렇게 붉은 글씨로 Warning!이 뜬 것을 확인할 수 있다.

 

이 방식으로 하면 HTML의 <a> tag의 valid atrribute가 아니라고 한다.

(사실 아직 이 부분을 정확히 이해 못함............. )

 

 

 

 

 

 

 

🌌그래서 일단 갑자기 해결책ㅎㅎ

 

1.

import { Link as ReactRouterDomLink } from "react-router-dom";

 

Link라는 이름을 나중에 따로 사용할 것이기 때문에

react-router-dom의 Link를 <ReactRouterDomLink/>라는 이름으로 import 해온다.

 

 

 

 

2.

const Link = ({children, isActive,...props }) => {
	return <ReactRouterDomLink {...props}>{children}</ReactRouterDomLink>;
};

Link라는 새로운 React component를 만든다. 

(styled-components가 아니고 일반 JSX를 리턴하는 리액트 컴포넌트!)

 

이 Link라는 컴포넌트는 props를 destructure해서

children을 받고

isActive와 나머지 props를 분리해서 받아서

 

아까 react-router-dom에서 import 해왔던 <ReactRouterDomLink/>를 return한다.

이때 isActive를 제외한 나머지 props와 children만 넣어준다.

그러면 (최종적으로) <a> tag에는 isActive라는 props가 전달되지 않는다.

그러면 이 isActive는 어떻게 되는 것인가?

 

 

 

 

 

3.

const StyledLink = styled(Link)`
	box-sizing: border-box;
	display: block;
	padding: 4px 8px;
	margin: 0 auto;
	text-align: center;

	font-weight: ${(props) => (props.isActive ? "bold" : "normal")};
`;

 

styled-components를 사용한 컴포넌트를 새로 만든다.

이름은 <StyledLink/>로 했고 styled(Link)`` 문법에서도 알 수 있듯이 

위에서 만들었던 <Link/> 컴포넌트의 모든 속성을 상속받는다.

그리고 그 속성들에 추가로

box-sizing, display, padding, margin, text-align 속성을 넣어주고

font-weight에서 드디어!!! isActive props를 사용한다.

이 isActive라는 props는

새로 만든 <Link /> 컴포넌트가 가지고는 있었지만(!) <ReactRouterDom/> 컴포넌트에 직접 전달하지는 않았기 때문에

아까와 같은 Warning! 오류는 뜨지 않는다.

그리고 <Link/> 컴포넌트가 어쨋든(!) 가지고 있던 isActive props는 <StyledLink/>가 상속을 받아서 스타일링할 때 사용할 수 있는 것.

 

 

 

 

 

 

4.

export default function Header() {
	return (
		<HeaderWrapper>
			<Menu>
				<StyledLink to="/">Home</StyledLink>
				<StyledLink to="/login" isActive>
					LogIn
				</StyledLink>
			</Menu>
		</HeaderWrapper>
	);
}

이제  <StyledLink/> 컴포넌트를 사용해서 링크들을 렌더링하면 된다.

(두 번째 StyledLink에 isActive라는 props를 넣었는데 오류가 뜨지 않는다. 

왜냐하면 이 isActive라는 props는 <StyledLink/> 컴포넌트에서만 사용하지 <Link/> 컴포넌트에서는 사용하지 않기 때문에!)

 

 

👩‍💻 전체 코드

import styled from "styled-components";
import { Link as ReactRouterDomLink } from "react-router-dom";

const HeaderWrapper = styled.header`
	box-sizing: border-box;
	display: flex;
	height: 60px;
	width: 100%;
	padding: 0 16px;
	position: fixed;
	top: 0;
`;

const Menu = styled.nav`
	display: flex;
	margin: auto 0 auto auto;
	font-family: "Open Sans";
	background: none;
	position: relative;
	top: initial;
	left: initial;
	width: initial;
	border-bottom: none;
`;

const Link = ({ isActive, children, ...props }) => {
	return <ReactRouterDomLink {...props}>{children}</ReactRouterDomLink>;
};

const StyledLink = styled(Link)`
	box-sizing: border-box;
	display: block;
	padding: 4px 8px;
	margin: 0 auto;
	text-align: center;

	font-weight: ${(props) => (props.isActive ? "bold" : "normal")};
`;

export default function Header() {
	return (
		<HeaderWrapper>
			<Menu>
				<StyledLink to="/">Home</StyledLink>
				<StyledLink to="/login" isActive>
					LogIn
				</StyledLink>
			</Menu>
		</HeaderWrapper>
	);
}

 

이렇게 하면 LogIn 부분이 bold 처리된 것을 확인할 수 있다.

 

 

 

 

 

 

5. 

이제 pathname이 '/'인 경우에는 Home이 bold, '/login'인 경우에는 Login이 bold처리 되게 만들 것!

이때 사용하는 것이 react-router-dom의 useLocation()이다.

 

import styled from "styled-components";
import { Link as ReactRouterDomLink, useLocation } from "react-router-dom";

const Link = ({ isActive, children, ...props }) => {
	return <ReactRouterDomLink {...props}>{children}</ReactRouterDomLink>;
};

const StyledLink = styled(Link)`
	box-sizing: border-box;
	display: block;
	padding: 4px 8px;
	margin: 0 auto;
	text-align: center;

	font-weight: ${(props) => (props.isActive ? "bold" : "normal")};
`;

export default function Header() {
	const { pathname } = useLocation();

	return (
			<>
				<StyledLink to="/" isActive={pathname === "/"}>
					Home
				</StyledLink>
				<StyledLink to="/login" isActive={pathname === "/login"}>
					LogIn
				</StyledLink>
			</>
	);
}

 

useLocation을 import해와서 pathname을 destructure한 다음에

각 <StyledLink/> 컴포넌트에서

pathname이 일치하는 경우 isActive props가 true, 아닌 경우 false가 되게 만들었다.

 

이제 Home에서는 pathname이 '/'니 Home이 bold처리가 될 것이고

LogIn을 클릭하면 pathname이 '/login'으로 바뀌니 LogIn이 bold처리가 될 것이다!

 

 

 

👩‍💻 전체 코드

import styled from "styled-components";
import { Link as ReactRouterDomLink, useLocation } from "react-router-dom";

const HeaderWrapper = styled.header`
	box-sizing: border-box;
	display: flex;
	height: 60px;
	width: 100%;
	padding: 0 16px;
	position: fixed;
	top: 0;
`;

const Menu = styled.nav`
	display: flex;
	margin: auto 0 auto auto;
	font-family: "Open Sans";
	background: none;
	position: relative;
	top: initial;
	left: initial;
	width: initial;
	border-bottom: none;
`;

const Link = ({ isActive, children, ...props }) => {
	return <ReactRouterDomLink {...props}>{children}</ReactRouterDomLink>;
};

const StyledLink = styled(Link)`
	box-sizing: border-box;
	display: block;
	padding: 4px 8px;
	margin: 0 auto;
	text-align: center;

	font-weight: ${(props) => (props.isActive ? "bold" : "normal")};
`;

export default function Header() {
	const { pathname } = useLocation();

	return (
		<HeaderWrapper>
			<Menu>
				<StyledLink to="/" isActive={pathname === "/"}>
					Home
				</StyledLink>
				<StyledLink to="/login" isActive={pathname === "/login"}>
					LogIn
				</StyledLink>
			</Menu>
		</HeaderWrapper>
	);
}

 

 

 

 

 

 

길고 길었다.

사실 아직도 오류를 정확히 이해하지 못한 것 같다.

이 방식이 조금 복잡하게 느껴져던 이유는

 

평소에는 react-router-dom의 <NavLink> 컴포넌트를 사용하기 때문이다.

(이름에서 눈치 챌 수 있는데 <NavLink>는 HTML 문서에서 단순히 <a> 태그를 사용하는 경우가 아니라, nav에서의 <a> 태그라는 느낌이 확-온다.)

 

 

 

 

 

🤩 <NavLink>

<Link>의 특별한 버전(?)으로 현재의 URL과 매치될 때의 스타일 속성을 따로 추가해줄 수 있다.

 

 

방법 1. activeClassName 속성 사용 (String)

<NavLink to="/faq" activeClassName="selected">
  FAQs
</NavLink>

URL이 "/faq"와 매치되는 경우

CSS 파일에 있는 .selected 클래스의 속성이 적용된다.

 

 

방법 2.  activeStyle 속성 사용 (Object)

<NavLink
  to="/faq"
  activeStyle={{
    fontWeight: "bold",
    color: "red"
  }}
>
  FAQs
</NavLink>

원하는 CSS 속성을 object에 담아서 (이때 camelCase 주의) 바로 넣어준다.

 

 

 

 

 

나는 주로 방법1을 사용해서 active인 경우의 스타일을 적용해 주었다.

아주 간단한 예시 코드

const StyledNavLink = styled(NavLink)`
	color:black;
    
    &.selected{
    	color:red;
    }
`;

 

 

 

 

 

그래서 다시 써 본 코드

import styled from "styled-components";
import { NavLink } from "react-router-dom";

const HeaderWrapper = styled.header`
	box-sizing: border-box;
	display: flex;
	height: 60px;
	width: 100%;
	padding: 0 16px;
	position: fixed;
	top: 0;
`;

const Menu = styled.nav`
	display: flex;
	margin: auto 0 auto auto;
	font-family: "Open Sans";
	background: none;
	position: relative;
	top: initial;
	left: initial;
	width: initial;
	border-bottom: none;
`;

const StyledNavLink = styled(NavLink)`
	box-sizing: border-box;
	display: block;
	padding: 4px 8px;
	margin: 0 auto;
	text-align: center;

	&.selected {
		font-weight: bold;
	}
`;

export default function Header() {
	return (
		<HeaderWrapper>
			<Menu>
				<StyledNavLink activeClassName="selected" exact to="/">
					Home
				</StyledNavLink>
				<StyledNavLink activeClassName="selected" to="/login">
					LogIn
				</StyledNavLink>
			</Menu>
		</HeaderWrapper>
	);
}

 

 

개인적으로 NavLink가 더 간결하고 이해하기 쉬운 것 같은데

혹시 다른 의견이 있으시다면 적극적으로 배우고 싶습니다🙇‍♀️