번외) react-router-dom의 Link를 styled-components로 꾸미기(feat.useLocation) (하지만 난 NavLink가 좋다)
이 포스트는 Udemy 사이트에 올라 온 Tom Phillips의 "React styled-components v5 (2021 edition)" 강의를 듣고 정리한 것입니다.
🔗링크
https://www.udemy.com/course/react-styled-components/
📝 시작점
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
이렇게 코드를 작성하면 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가 더 간결하고 이해하기 쉬운 것 같은데
혹시 다른 의견이 있으시다면 적극적으로 배우고 싶습니다🙇♀️