Skip to main content

Command Palette

Search for a command to run...

建立響應式選單,使用 React 和 Styled Components

簡單筆記,僅供自己腦內復健使用,若指令資料有錯,請再查證。

Published
•View as Markdown
建立響應式選單,使用 React 和 Styled Components
Y

一言敝之就是職業婦女。

安裝 套件

首先先透過安裝 vite

npm crate vite@lastest

再來安裝react route 與 style-components

npm i react-router-dom styled-components

安裝 fontAwesome for react.js

按照此 fontAwesome 的 step 安裝以下套件

# Add SVG Core
npm i --save @fortawesome/fontawesome-svg-core
# Free icons styles
npm i --save @fortawesome/free-solid-svg-icons
npm i --save @fortawesome/free-regular-svg-icons
npm i --save @fortawesome/free-brands-svg-icons
# Add the React Component
npm i --save @fortawesome/react-fontawesome@latest

建立 router

預計建立Home About Blog Services Sign up 頁面

在 src > 建立 pages 資料夾,依序建立 Home.jsx About.jsx Blog.jsx Services.jsx Sign-up.jsx ,建立好頁面時,直接在頁面輸入 rafc 就會自動引入 react.js template

import React from 'react'

export const main = () => {
  return (
    <div>Home</div>
  )
}

在 main.jsx 刪除原本示範頁內容,import react-route-dom ,並建立 router 路徑

import { createBrowserRouter, RouterProvider } from 'react-router-dom'

const router = createBrowserRouter([
  {
    path: "/",
    element: <App />,
    children: [
      {
        path: '',
        element: <Home />
      },
      { path: '/about', element: <About /> },
      { path: '/blog', element: <Blog /> },
      { path: '/services', element: <Services /> },
      { path: '/sign-up', element: <SignUp />}
  ]}
])

在 App.jsx 將結構放入此頁面

import { Outlet } from 'react-router-dom'
import { Navbar } from './components/Navbar'

function App() {
  return (
    <div className="App">
      <Navbar />
      <Outlet />
    </div>
  )
}

這裡有用到 Outlet ,因為實在太出戲(笑),一直想到某某購物中心 outlet 查了下相關解釋

在 React 中,使用 import { Outlet } from "react-router-dom" 是與 React Router 函式庫相關的。Outlet 組件在 React Router 中扮演著特別的角色。它被用於組件內部,用於渲染當前路由的子路由組件。

這是它的一些主要用途:

  1. 子路由渲染:當你有一個路由配置,其中某些路由是其他路由的子路由時,Outlet 組件用於渲染這些子路由。你可以在父路由組件中放置一個 Outlet,它將自動渲染與當前 URL 匹配的任何子路由。

  2. 布局組件:在創建具有共同布局的多個頁面時(例如,共享相同的頁首和頁尾),Outlet 可以用來在布局中的指定位置渲染子頁面或子組件。

  3. 代碼組織:通過使用 Outlet,你可以在路由配置中更清晰地組織父子路由關係,使代碼更加模組化和易於維護。

簡單來說,Outlet 類似於一個佔位符,告訴 React Router 在哪裡渲染子路由的內容。

在 src > 建立 styles 資料夾,建立 Global.styled.jsx 全局樣式

這裡有使用 google font 的 Poppins

https://fonts.google.com/specimen/Poppins

因為 react.js 寫 style css 是包在``樣板中進行撰寫,打錯字要 debug 要花時間,
所以基本上寫 CSS 需要 auto complete 協助自動補完或是 Intelligence,
所以這邊就推薦安裝VS Code 套件:

vscode-styled-components

import { createGlobalStyle } from 'styled-components'

const GlobalStyles = createGlobalStyle`
body {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: 'Poppins', sans-self;
}
`
export default GlobalStyles;

定義好後並放入App.jsx

import { Outlet } from 'react-router-dom'
import { Navbar } from './components/Navbar'
import GlobalStyles from './styles/Global.styled'

function App() {
  return (
    <div className="App">
      <Navbar />
      <Outlet />
      <GlobalStyles />
    </div>
  )
}

如此以上都沒有任何噴錯,應該可以看到最基礎透過 react.js route 建立的 router💫

接下來需要處理 NavBar 的相關樣式,首先先定義 logo 樣式

可先在 logoipsum 下載示範 logo,並下載到 >assets 下

logoipsum

在 src > styles > Navbar.styled.jsx 定義 logo 樣式

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { NavLink } from 'react-router-dom'
import styled from 'styled-components'

const LogoImg = styled.img`
  align-self: flex-start;
  width: 150px;
`

export { LogoImg }

在 logo.jsx 導入 logo,和寫好的樣式

import React from 'react'
import { LogoImg } from '../styles/Navbar.styled'
import logo from '../assets/logo.svg'

export const Logo = () => {
  return <LogoImg src={logo} alt='logo'/>
}

有了基本的 logo 樣式後,就可以直接在 NavBar.jsx 寫相關結構

import { Logo } from './Logo'
import { NavLinkWrapper, NavbarWrapper, StyledNavLink } from '../styles/Navbar.styled'

export const Navbar = () => {
  const link = [
    {
      page: 'Home',
      href: '/',
    },
    { page: 'About', href: '/about' },
    { page: 'Blog', href: '/blog' },
    { page: 'Services', href: '/services' },
    { page: 'Sign up', href: '/sign-up' },

  ]
  return (
    <NavbarWrapper>
      <Logo />
      <NavLinkWrapper>
        {link.map((link) => (
          <StyledNavLink 
            activeclassname='active' 
            key={link.page} 
            to={link.href}>
            {link.page}
          </StyledNavLink>
        ))}
        <div>{active}</div>
      </NavLinkWrapper>
    </NavbarWrapper>
  )
}

這裡會將NavbarWrapper NavLinkWrapper 樣式都寫在 styles/Navbar.styled

import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { NavLink } from "react-router-dom";
import styled from "styled-components";

const LogoImg = styled.img`
  align-self: flex-start;
  width: 150px;
`

const NavbarWrapper = styled.nav`
  display: flex;
  justify-content: space-between;
  align-items: center;
  background-color: #000;
  padding: 1rem 3rem;
  position: relative;
  @media (max-width: 704px) {
    flex-direction: column;
    padding: 1rem;
  }
`

const NavLinkWrapper = styled.div`
@media (max-width: 704px) {
  // react 中可以直接透過 props 傳值改變 css 樣式
  display: ${(props) => (props.active ? 'block' : 'none')};
  text-align: center;
  padding: 2rem 0;
}
`

const StyledNavLink = styled(NavLink)`
  text-decoration: none;
  transition: 0.2s;
  color: white;
  margin-left: 2rem;
  &:hover {
    color: #007dfc;
  }
  &:last-child {
    background-color: #007dfc;
    padding: 0.5rem 1rem;
    border-radius: 30px;
     &:hover {
      color: white;
    }
    &.${(props) => props.activeclassname} {
      color: white;
    }
  }
  &.${(props) => props.activeclassname} {
    color: #007dfc;
  }
  @media (max-width: 704px) {
    display: block;
    margin: 2rem auto;
  }
`

const StyledFontAwesomeIcon = styled(FontAwesomeIcon)`
  display: none;
  position: absolute;
  right: 20px;
  top: 21px;
  color: white;
  font-size: 1.8rem;
  cursor: pointer;
  @media (max-width: 704px)  {
    display: block;
  }
`

export { LogoImg, NavbarWrapper, NavLinkWrapper, StyledNavLink, StyledFontAwesomeIcon }

最後需要調整漢堡選單相關樣式與 active 的樣式與寫法 NavBar.jsx

import React, { useState } from 'react'
import { Logo } from './Logo'
import { NavLinkWrapper, NavbarWrapper, StyledNavLink, StyledFontAwesomeIcon } from '../styles/Navbar.styled'
// 從 fortawesome 引入漢堡選單 icon
import {faBars} from '@fortawesome/free-solid-svg-icons'

export const Navbar = () => {

  const [active, setActive] = useState(false)
  const link = [
    {
      page: 'Home',
      href: '/',
    },
    { page: 'About', href: '/about' },
    { page: 'Blog', href: '/blog' },
    { page: 'Services', href: '/services' },
    { page: 'Sign up', href: '/sign-up' },

  ]
  return (
    <NavbarWrapper>
      <Logo />
      <StyledFontAwesomeIcon icon={faBars} onClick={() => setActive(!active)}/>
      <NavLinkWrapper active={active}>
        {link.map((link) => (
          <StyledNavLink 
            activeclassname='active' 
            key={link.page} 
            to={link.href}>
            {link.page}
          </StyledNavLink>
        ))}
        <div>{active}</div>
      </NavLinkWrapper>
    </NavbarWrapper>
  )
}

useState 是 React 框架中一個非常重要的 Hook,用於在函數組件中添加狀態管理功能。

使用 useState 時,它允許在函數組件中定義一個狀態類型。這個狀態類型可以是任何類型,如數字、字串、物件或陣列等。useState 還提供一個函數來更新這個狀態,當狀態更新時,組件將重新渲染,以反應新的狀態。

在寫此段時我遇到一項錯誤

出現以下錯誤
Warning: Received `false` for a non-boolean attribute `active`.
If you want to write it to the DOM, pass a string instead: active="false" or active={value.toString()}.
const NavLinkWrapper = styled.div`
@media (max-width: 704px) {
  // react 中可以直接透過 props 傳值改變 css 樣式
  display: ${(props) => (props.active ? 'block' : 'none')};
  text-align: center;
  padding: 2rem 0;
}
`

主要是我寫此段,習慣性用 VUE.js 寫法,
如: display: ${(props) => { props.active ? 'block' : 'none'} } <= 這個是錯誤寫法
我直接習慣寫成 {} 而非 () 導致一直噴此錯誤XD

以上,簡單的回顧順便梳理一下流程,基本上只寫一次只能感受到和 Vue-Router 好像有像又好像不太像的地方:P

以上的教學來自於:

React Responsive Navbar Tutorial with Styled Components

More from this blog

Uncaught Error: Minified React error #329 發生以及可能解決方向

重點:Minified React error #329(Unknown root exit status)常見於 SSR 與 CSR 首次渲染輸出不一致(資料或節點結構不同)。 1. 現象 狀況條件表徵 首次載入/重新整理偶發錯誤無法重現的環境使用慢速網路(3G) + 可重現的環境機率出現Console 拋 #329 首屏閃動 / 全量替換SSR 已渲染列表, CSR 初始為空UI 閃爍/卡住/無動作 浮動元件顯示異常條件 mount 多個節點位置跳動 / 佈局不穩 2...

Nov 17, 2025

Redmine 好用的 Raycast 插件與自製好用 plug-in

我是 Raycast 的愛用者 <3 工作上常需要使用 Eedmine 切卡,看 task 之類的,我個人偏好Asana or Jira 自動化和更完整串流整個公司流程的任務,可本人就是適應力強,縱使覺得不太習慣的工作(謙虛的說法)也會想辦法搞得好用 推薦一個插件,和我自己寫的 plug-in jwicker 寫的 Raycast Redmine https://www.raycast.com/jwickers/redmine 此插件可以在 Raycast 中快速搜尋並開啟 Redmine 的 ...

Nov 10, 2025

前端踩坑實錄:用 user-scalable=0 擋放大,還有哪些更溫柔的做法?

其實沒有,還是只有那兩招。 這個議題早些年常遇到,即時現在還是常遇到(?還是一樣重寫解法,專案中有搜尋部分,在特定型號 iOS 18.4, iPhone 16 Plus 再點開搜尋(input) 會自動放大,如下圖,我應該都有好好碼到吧(? 會發現 input 焦點後,畫面會被 zoom in 放大,主要原因 行動版(特別是 iOS Safari)特定版本下(比如本案是在 iOS 18.4, iPhone 16 Plus下) 在點擊 input/textarea 時,若字體小於 16px,...

Nov 7, 2025

切一下,世界安靜了:aten 的 Kvm Us3312 讓我少罵三句幹的雙系統配置術

目前敝司環境主要配置一台 windows 筆電(收信使用:P)和我自己的 Mac air,還有一台 BenQ 27吋的螢幕,Logi K855 + Logi Lift,除了 window 筆電,其他都是我自備的 純粹就是我想搭配白色系的桌面:P 最開始我是把 window 收在置物櫃,因 windows 筆電公司有鎖藍牙,所以無法透過K855 + Lift 多頻切換方式去連線,我也不想透過 usb 方式連線鍵鼠組,所以早期透過 Mac air APP 「Windows APP」遠端連線到 wi...

Oct 30, 2025

Yukki的腦洞很大

13 posts

職業婦女,前端工程師,

白天上日班(工作),晚上上晚班(家庭)

日夜笙歌。