Next - 專案初始化
初始化專案
- 安裝 Next.js
pnpm dlx create-next-app@latest
- 安裝依賴套件
pnpm install
- 啟動專案
pnpm run dev
warning
注意一下喔,Docusaurus 的專案啟動關鍵字是傳統 React 的 start
,但 Next 的專案啟動關鍵字跟 Nuxt3 一樣是 dev
。
tip
現在很多人都很愛用 SCSS 來寫 CSS,所以需要的話請額外安裝:
pnpm install sass
安裝 Tailwind CSS 跟 Eslint
跟 vue 的 Nuxt3 不一樣,Next.js 不需要自己安裝 Tailwind CSS 跟 Eslint,在初始化專案時就會詢問是否要安裝。
What is your project named? my-app
Would you like to use TypeScript? No / Yes
Would you like to use ESLint? No / Yes
Would you like to use Tailwind CSS? No / Yes
Would you like to use `src/` directory? No / Yes
Would you like to use App Router? (recommended) No / Yes
Would you like to customize the default import alias (@/*)? No / Yes
What import alias would you like configured? @/*
安裝 Prettier
- 安裝 Prettier 跟插件:
prettier
: 主要的代碼格式化工具。prettier-plugin-tailwindcss
: 整合Tailwind CSS。eslint-config-prettier
: 關閉所有不必要或可能與 Prettier 衝突的ESLint規則。eslint-plugin-prettier
: 將Prettier作為ESLint規則運行。
pnpm install -D prettier prettier-plugin-tailwindcss eslint-config-prettier eslint-plugin-prettier
- 建立 .prettierrc.cjs 檔案:
module.exports = {
plugins: [
'prettier-plugin-tailwindcss'
],
printWidth: 80,
semi: false,
singleQuote: true,
trailingComma: 'none',
bracketSpacing: true,
arrowParens: "always",
bracketSameLine: false,
endOfLine: "auto",
htmlFormat: "auto",
javascriptFormat: "auto",
}
- 前往 .eslintrc.json 檔案,加入 Prettier 設定:
{
"extends": ["next/core-web-vitals", "prettier"],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error"
}
}
Stylelint
- 安裝 Stylelint:
pnpm install --save-dev stylelint stylelint-config-standard
- 安裝 SCSS 規範:
pnpm install --save-dev stylelint-config-standard-scss stylelint-order postcss postcss-scss postcss-html
- root 下建立 .stylelintrc.cjs 並輸入:
完整配置
module.exports = {
extends: [
"stylelint-config-standard",
'stylelint-config-standard-scss'
],
plugins: [
'stylelint-order'
],
"fix": true, // 自動修復
overrides: [
{
files: [ '*.scss', '**/*.scss'],
rules: {
'scss/no-global-function-names': null // 關閉 scss 全域函式名稱檢查
}
}
],
rules: {
'unit-allowed-list': [ 'em', 'rem', 'deg', 'px', 'vh', 'vw', '%', 's', 'ms', 'fr' ],
'order/properties-order': [
'position',
'top',
'bottom',
'right',
'left',
'display',
'align-items',
'justify-content',
'float',
'clear',
'overflow',
'overflow-x',
'overflow-y',
'margin',
'margin-top',
'margin-right',
'margin-bottom',
'margin-left',
'padding',
'padding-top',
'padding-right',
'padding-bottom',
'padding-left',
'width',
'min-width',
'max-width',
'height',
'min-height',
'max-height',
'font-size',
'font-family',
'font-weight',
'text-align',
'text-justify',
'text-indent',
'text-overflow',
'text-decoration',
'white-space',
'color',
'background',
'background-position',
'background-repeat',
'background-size',
'background-color',
'background-clip',
'border',
'border-style',
'border-width',
'border-color',
'border-top-style',
'border-top-width',
'border-top-color',
'border-right-style',
'border-right-width',
'border-right-color',
'border-bottom-style',
'border-bottom-width',
'border-bottom-color',
'border-left-style',
'border-left-width',
'border-left-color',
'border-radius',
'opacity',
'filter',
'list-style',
'outline',
'visibility',
'z-index',
'box-shadow',
'text-shadow',
'resize',
'transition'
]
}
}
Redux - 全域狀態管理
- Install
pnpm add @reduxjs/toolkit react-redux
- Define Root State and Dispatch Types 資料夾結構如下:
---| app
---| lib
------| store.ts
lib/store.ts
import { configureStore } from '@reduxjs/toolkit'
export const makeStore = () => {
return configureStore({
reducer: {}
})
}
// Infer the type of makeStore
export type AppStore = ReturnType<typeof makeStore>
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<AppStore['getState']>
export type AppDispatch = AppStore['dispatch']
- Define Typed Hooks 為了之後方便使用上述定義的型別,需要再進行下列配置:
lib/hooks.ts
import { useDispatch, useSelector, useStore } from 'react-redux'
import type { AppDispatch, AppStore, RootState } from './store'
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()
export const useAppStore = useStore.withTypes<AppStore>()
- Providing the Store
app/StoreProvider.tsx
'use client'
import { useRef } from 'react'
import { Provider } from 'react-redux'
import { makeStore, AppStore } from '../lib/store'
export default function StoreProvider({
children
}: {
children: React.ReactNode
}) {
const storeRef = useRef<AppStore>()
if (!storeRef.current) {
// Create the store instance the first time this renders
storeRef.current = makeStore()
}
return <Provider store={storeRef.current}>{children}</Provider>
}
- Include the
StoreProvider
舉例來說,可以在全局 layout 配置StoreProvider
以共享 store 狀態,或單獨使用在需要使用特定 store 的特定組件上。
/app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import StoreProvider from "./StoreProvider";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>
<StoreProvider>
{children}
</StoreProvider>
</body>
</html>
);
}
- Create a Redux State Slice 資料夾結構如下:
---| lib
------| features
---------| counter
------------| counterSlice.ts
/lib/features/counter/counterSlice.ts
import { createSlice } from '@reduxjs/toolkit'
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment: state => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.value += 1
},
decrement: state => {
state.value -= 1
},
incrementByAmount: (state, action) => {
state.value += action.payload
}
}
})
// Action creators are generated for each case reducer function
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer
- Add Slice Reducers to the Store
lib/store.ts
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './features/counter/counterSlice'
export default configureStore({
reducer: {
counter: counterReducer
}
})
- Use Redux State and Actions in React Components 資料夾結構:
---| app
------| /app/page.tsx
------| display
/app/page.tsx
'use client'
import Link from 'next/link';
import { useAppSelector, useAppDispatch } from '../lib/hooks'
import { decrement, increment } from '@/lib/features/counter/counterSlice';
export default function Home() {
const count = useAppSelector((state) => state.counter.value)
const dispatch = useAppDispatch()
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div className="z-10 w-full max-w-5xl items-center justify-between font-mono text-sm lg:flex">
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
Increment
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
Decrement
</button>
<Link href="/display">
<p>Display Page</p>
</Link>
</div>
</main>
);
}
/app/display/page.tsx
'use client'
import { useAppSelector, useAppDispatch } from '@/lib/hooks'
import Link from 'next/link'
export default function Page(){
const count = useAppSelector((state) => state.counter.value)
console.log(count)
const dispatch = useAppDispatch()
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<p>Here is the number change from Home page</p>
<p>{count}</p>
<Link href="/">
<p>Home Page</p>
</Link>
</main>
)
}
安裝 NextUI
pnpm add @nextui-org/react framer-motion
- Tailwind CSS Setup
tailwind.config.js
// tailwind.config.js
import {nextui} from "@nextui-org/react";
/** @type {import('tailwindcss').Config} */
const config = {
content: [
// ...
"./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}"
],
theme: {
extend: {},
},
darkMode: "class",
plugins: [nextui()]
}
export default config;
- Setup Provider 建立 app/providers.tsx:
app/providers.tsx
// app/providers.tsx
'use client'
import {NextUIProvider} from '@nextui-org/react'
export function Providers({children}: { children: React.ReactNode }) {
return (
<NextUIProvider>
{children}
</NextUIProvider>
)
}
- Add Provider to Root
app/layout.tsx
// app/layout.tsx
import {Providers} from "./providers";
export default function RootLayout({children}: { children: React.ReactNode }) {
return (
<html lang="en" className='dark'>
<body>
<Providers>
{children}
</Providers>
</body>
</html>
);
}
- Setup pnpm (optional) 使用 pnpm 需要額外進行此項設定:
.npmrc
public-hoist-pattern[]=*@nextui-org/*
- 設定完成,嘗試使用
app/page.tsx
// app/page.tsx
import { Button } from '@nextui-org/button'
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div>
<Button>Click me</Button>
</div>
</main>
)
}
warning
如果出現這個錯誤:找不到模組 '@nextui-org/button' 或其對應的型別宣告,可以嘗試再一次 pnpm install
。