1、項(xiàng)目搭建
1、創(chuàng)建項(xiàng)目
- 1、該項(xiàng)目使用的是ts創(chuàng)建的 所以需要加上
--template typescript
-
create-react-app kiki_ts_react_music --template typescript
- 2、整理項(xiàng)目結(jié)構(gòu) 刪除一些自己用不到的文件
-
1.2 配置項(xiàng)目
1.2.1 更換icon
1.2.2 更換項(xiàng)目名稱
在index.html文件里面
1.2.1 配置項(xiàng)目別名
- 1、
npm i -D @craco/craco
- 2、在根文件創(chuàng)建
craco.config.ts
const path = require("path");
const CracoLessPlugin = require("craco-less");
// path.resolve返回當(dāng)前文件的絕對(duì)路徑 拼接+dir
const resolve = (dir) => path.resolve(__dirname, dir);
module.exports = {
plugins: [{ plugin: CracoLessPlugin }],
webpack: {
alias: {
"@": resolve("src"),
},
},
};
- 3、修改
tsconfig.json
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
- 4、修改
package.json
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject"
},
1.3 代碼規(guī)范
1.3.1 集成editorconfig配置
EditorConfig 有助于為不同 IDE 編輯器上處理同一項(xiàng)目的多個(gè)開發(fā)人員維護(hù)一致的編碼風(fēng)格。
- 1、在根目錄下創(chuàng)建
.editorconfig
文件
# http://editorconfig.org
root = true
[*] # 表示所有文件適用
charset = utf-8 # 設(shè)置文件字符集為 utf-8
indent_style = space # 縮進(jìn)風(fēng)格(tab | space)
indent_size = 2 # 縮進(jìn)大小
end_of_line = lf # 控制換行類型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行尾的任意空白字符
insert_final_newline = true # 始終在文件末尾插入一個(gè)新行
[*.md] # 表示僅 md 文件適用以下規(guī)則
max_line_length = off
trim_trailing_whitespace = false
**同時(shí)需要安裝插件 **EditorConfig for VS Code
1.3.2 使用prettier工具
Prettier 是一款強(qiáng)大的代碼格式化工具,支持 JavaScript、TypeScript、CSS、SCSS、Less、JSX、Angular、Vue、GraphQL、JSON、Markdown 等語言,基本上前端能用到的文件格式它都可以搞定,是當(dāng)下最流行的代碼格式化工具。
-
1.安裝prettier
npm install prettier -D
-
2、配置.prettierrc文件:
在根目錄下創(chuàng)建該文件
{
"useTabs": false,
"tabWidth": 2,
"printWidth": 80,
"singleQuote": true,
"trailingComma": "none",
"semi": false
}
- 3、創(chuàng)建.prettierignore忽略文件
在根目錄下
/dist/*
.local
.output.js
/node_modules/**
**/*.svg
**/*.sh
/public/*
- 4、在package.json中配置一個(gè)scripts:
"prettier": "prettier --write ."
執(zhí)行 npm run prettier
就會(huì)將項(xiàng)目全部按照prettier的配置進(jìn)行格式化
1.4 項(xiàng)目結(jié)構(gòu)
1.5 對(duì)css進(jìn)行重置
-
1、下載normalize.css
cnpm install normalize.css
在index.tsx里面引入import 'normalize.css'
-
2、使用less
cnpm install craco-less
const path = require('path')
const CracoLessPlugin = require('craco-less')
const resolve = (dir) => path.resolve(__dirname, dir)
module.exports = {
plugins: [{ plugin: CracoLessPlugin }],
webpack: {
alias: {
'@': resolve('src')
}
}
}
- 3、配置自定義的css
最后都在index.jsx中引入
import 'normalize.css'
import '@/assets/css/index.less'
1.6 注入router
npm install react-router-dom
-
在tsx中 使用到dom的頁面都需要引入
import React from 'react'
-
router/index.tsx
import React from 'react'
import type { RouteObject } from 'react-router-dom'
import Discover from '@/views/discover'
import Mime from '@/views/mime'
const routes: RouteObject[] = [
{ path: '/', element: <Mime /> },
{ path: '/discover', element: <Discover /> }
]
export default routes
- index.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from '@/App'
import { BrowserRouter } from 'react-router-dom'
import 'normalize.css'
import '@/assets/css/index.less'
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
)
- index.tsx
import React, { Suspense } from 'react'
import { Link, useRoutes } from 'react-router-dom'
import routes from './router'
function App() {
return (
<div className="App">
<header className="App-header">
<h1>hahah</h1>
<Link to="/discover">發(fā)現(xiàn)音樂</Link>
<Suspense fallback="正在加載">{useRoutes(routes)}</Suspense>
</header>
</div>
)
}
export default App
1.7 定義TS組件的規(guī)范
import React, { memo } from 'react'
import type { ReactNode } from 'react'
// 定義傳進(jìn)來的props類型
interface IProps {
// 在之前的版本props默認(rèn)會(huì)有children是插槽 在后來取消了得自己寫
children?: ReactNode
name?: string
age?: number
}
const Download: React.FC<IProps> = (props) => {
return (
<div>
{props.children}
<h1>{props.age}</h1>
<h1>{props.name}</h1>
</div>
)
}
export default memo(Download)
import React, { Suspense } from 'react'
import { Link, useRoutes } from 'react-router-dom'
import routes from './router'
import Download from './views/download'
function App() {
return (
<div className="App">
<header className="App-header">
<h1>hahah</h1>
<Link to="/discover">發(fā)現(xiàn)音樂</Link>
<Download name="kiki">
<h1>我是downLoad的插槽</h1>
</Download>
<Suspense fallback="正在加載">{useRoutes(routes)}</Suspense>
</header>
</div>
)
}
export default App
1.8 創(chuàng)建代碼片段
首選項(xiàng)=>設(shè)置代碼片段=>react-ts
生成代碼片段的網(wǎng)站
https://snippet-generator.app/?description=&tabtrigger=&snippet=&mode=vscode
import React, { memo } from 'react'
import type { FC, ReactNode } from 'react'
interface IProps {
children?: ReactNode
}
const Template: FC<IProps> = () => {
return <div>Template</div>
}
export default memo(Template)
1.9 二級(jí)路由和懶加載
- discover頁面
import React, { memo, Suspense } from 'react'
import type { FC, ReactNode } from 'react'
import { Outlet, Link } from 'react-router-dom'
interface IProps {
children?: ReactNode
}
const Discover: FC<IProps> = () => {
return (
<div>
<div>
<Link to="/discover/recommend">推薦</Link>
<Link to="/discover/ranking">排行榜</Link>
<Link to="/discover/songs">歌單</Link>
<Link to="/discover/djradio">主播電臺(tái)</Link>
<Link to="/discover/artist">歌手</Link>
<Link to="/discover/album">新碟上架</Link>
</div>
{/* 二級(jí)路由也可以用suspense */}
<Suspense fallback="正在加載">
<Outlet />
</Suspense>
</div>
)
}
export default memo(Discover)
- App.jsx
import React, { Suspense } from 'react'
import { Link, useRoutes } from 'react-router-dom'
import routes from './router'
import Download from './views/download'
function App() {
return (
<div className="App">
<div className="nav">
<Link to="/discover">發(fā)現(xiàn)音樂</Link>
<Link to="/mine">我的音樂</Link>
<Link to="/focus">關(guān)注</Link>
<Link to="/download">下載客戶端</Link>
</div>
<Suspense fallback="正在加載">{useRoutes(routes)}</Suspense>
<div className="main"></div>
</div>
)
}
export default App
1.10 redux-reduxtk
cnpm install @reduxjs/toolkit react-redux
- index.tsx 提供Provide
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from '@/App'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import 'normalize.css'
import '@/assets/css/index.less'
import store from './store'
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
root.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
)
- store/index.ts
import { configureStore } from '@reduxjs/toolkit'
import { useSelector, useDispatch, TypedUseSelectorHook } from 'react-redux'
import counterReducer from './modules/counter'
const store = configureStore({
reducer: {
counter: counterReducer
}
})
// 獲取函數(shù)的返回類型
type GetStateFnType = typeof store.getState
// 獲取函數(shù)返回類型的類型
type IRootState = ReturnType<GetStateFnType>
type DispatchType = typeof store.dispatch
export const useAppSelector: TypedUseSelectorHook<IRootState> = useSelector
export const useAppDisPatch: () => DispatchType = useDispatch
export default store
- store/count.ts
import { createSlice } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initialState: {
count: 1,
message: 'hello'
},
reducers: {
changeMessageAction(state, { payload }) {
state.message = payload
}
}
})
export const { changeMessageAction } = counterSlice.actions
export default counterSlice.reducer
- 使用的頁面
import React, { memo, Suspense } from 'react'
import type { FC, ReactNode } from 'react'
import { Outlet, Link } from 'react-router-dom'
import { useAppDisPatch, useAppSelector } from '@/store'
import { shallowEqual } from 'react-redux'
import { changeMessageAction } from '@/store/modules/counter'
interface IProps {
children?: ReactNode
}
const Discover: FC<IProps> = () => {
const { count, message } = useAppSelector(
(state) => ({
count: state.counter.count,
message: state.counter.message
}),
shallowEqual
)
const dispatch = useAppDisPatch()
const changeMessage = (message: string) => {
dispatch(changeMessageAction(message))
}
return (
<div>
<div>
{count}=={message}
<button onClick={() => changeMessage('修改message')}>
修改message
</button>
<Link to="/discover/recommend">推薦</Link>
<Link to="/discover/ranking">排行榜</Link>
<Link to="/discover/songs">歌單</Link>
<Link to="/discover/djradio">主播電臺(tái)</Link>
<Link to="/discover/artist">歌手</Link>
<Link to="/discover/album">新碟上架</Link>
</div>
{/* 二級(jí)路由也可以用suspense */}
<Suspense fallback="正在加載">
<Outlet />
</Suspense>
</div>
)
}
export default memo(Discover)
1.10 axios的封裝
- request/index.ts
import axios from 'axios'
import type { AxiosInstance } from 'axios'
import type { HYRequestConfig } from './type'
// 攔截器: 蒙版Loading/token/修改配置
/**
* 兩個(gè)難點(diǎn):
* 1.攔截器進(jìn)行精細(xì)控制
* > 全局?jǐn)r截器
* > 實(shí)例攔截器
* > 單次請(qǐng)求攔截器
*
* 2.響應(yīng)結(jié)果的類型處理(泛型)
*/
class HYRequest {
instance: AxiosInstance
// request實(shí)例 => axios的實(shí)例
constructor(config: any) {
this.instance = axios.create(config)
// 每個(gè)instance實(shí)例都添加攔截器
this.instance.interceptors.request.use(
(config) => {
// loading/token
return config
},
(err) => {
return err
}
)
this.instance.interceptors.response.use(
(res) => {
return res.data
},
(err) => {
return err
}
)
// 針對(duì)特定的hyRequest實(shí)例添加攔截器
this.instance.interceptors.request.use(
config.interceptors?.requestSuccessFn,
config.interceptors?.requestFailureFn
)
this.instance.interceptors.response.use(
config.interceptors?.responseSuccessFn,
config.interceptors?.responseFailureFn
)
}
// 封裝網(wǎng)絡(luò)請(qǐng)求的方法
// T => IHomeData
request<T = any>(config: HYRequestConfig<T>) {
// 單次請(qǐng)求的成功攔截處理
if (config.interceptors?.requestSuccessFn) {
config = config.interceptors.requestSuccessFn(config)
}
// 返回Promise
return new Promise<T>((resolve, reject) => {
this.instance
.request<any, T>(config)
.then((res) => {
// 單詞響應(yīng)的成功攔截處理
if (config.interceptors?.responseSuccessFn) {
res = config.interceptors.responseSuccessFn(res)
}
resolve(res)
})
.catch((err) => {
reject(err)
})
})
}
get<T = any>(config: HYRequestConfig<T>) {
return this.request({ ...config, method: 'GET' })
}
post<T = any>(config: HYRequestConfig<T>) {
return this.request({ ...config, method: 'POST' })
}
delete<T = any>(config: HYRequestConfig<T>) {
return this.request({ ...config, method: 'DELETE' })
}
patch<T = any>(config: HYRequestConfig<T>) {
return this.request({ ...config, method: 'PATCH' })
}
}
export default HYRequest
- request/type.ts
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
// 針對(duì)AxiosRequestConfig配置進(jìn)行擴(kuò)展
export interface HYInterceptors<T = AxiosResponse> {
requestSuccessFn?: (config: AxiosRequestConfig) => AxiosRequestConfig
requestFailureFn?: (err: any) => any
responseSuccessFn?: (res: T) => T
responseFailureFn?: (err: any) => any
}
export interface HYRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
interceptors?: HYInterceptors<T>
}
- config/index.ts
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
// 針對(duì)AxiosRequestConfig配置進(jìn)行擴(kuò)展
export interface HYInterceptors<T = AxiosResponse> {
requestSuccessFn?: (config: AxiosRequestConfig) => AxiosRequestConfig
requestFailureFn?: (err: any) => any
responseSuccessFn?: (res: T) => T
responseFailureFn?: (err: any) => any
}
export interface HYRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
interceptors?: HYInterceptors<T>
}
環(huán)境變量也可以通過配置文件 但是前面需要加上REACT_APP_…
文章來源:http://www.zghlxwxcb.cn/news/detail-826319.html
- service/index.ts
import { BASE_URL, TIME_OUT } from './config'
import HYRequest from './request'
const hyRequest = new HYRequest({
baseURL: BASE_URL,
timeout: TIME_OUT,
interceptors: {
requestSuccessFn: (config: any) => {
return config
}
}
})
export default hyRequest
文章來源地址http://www.zghlxwxcb.cn/news/detail-826319.html
- 使用的頁面
import React, { memo, useEffect, useState } from 'react'
import type { FC, ReactNode } from 'react'
import hyRequest from '@/service'
interface IProps {
children?: ReactNode
}
export interface IBannerData {
imageUrl: string
targetId: number
targetType: number
titleColor: string
typeTitle: string
url: string
exclusive: boolean
scm: string
bannerBizType: string
}
const Recommend: FC<IProps> = () => {
const [banners, setBanners] = useState<IBannerData[]>([])
// 測(cè)試網(wǎng)絡(luò)請(qǐng)求
useEffect(() => {
hyRequest
.get({
url: '/banner'
})
.then((res) => {
setBanners(res.banners)
})
}, [])
return (
<div>
{banners.map((item, index) => {
return <div key={index}>{item.imageUrl}</div>
})}
</div>
)
}
export default memo(Recommend)
- 可以在這個(gè)頁面自動(dòng)生成類型定義
https://transform.tools/json-to-typescript
1.11 類組件和TS的結(jié)合
import React, { PureComponent } from 'react'
/**
* state:
* props:
*/
interface IProps {
name: string
age?: number
}
interface IState {
message: string
counter: number
}
class Demo02 extends PureComponent<IProps, IState> {
name = 'aaaa'
state = {
message: 'Hello World',
counter: 99
}
// getSnapshotBeforeUpdate() {
// return { address: '廬山' }
// }
// componentDidUpdate(
// prevProps: Readonly<IProps>,
// prevState: Readonly<IState>,
// snapshot?: ISnapshot | undefined
// ): void {}
// constructor(props: IProps) {
// super(props)
// // this.state = {
// // message: 'Hello World',
// // counter: 100
// // }
// }
render(): React.ReactNode {
return (
<div>
name: {this.props.name}
age: {this.props.age}
message: {this.state.message}
counter: {this.state.counter}
</div>
)
}
}
export default Demo02
1.12 redux和ts的結(jié)合
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
interface IState {
count: number
message: string
address: string
height: number
direction: 'left' | 'right' | 'up' | 'down'
names: string[]
}
const initialState: IState = {
count: 100,
message: 'Hello Redux',
address: '廣州市',
height: 1.88,
direction: 'left',
names: []
}
const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
changeMessageAction(state, { payload }: PayloadAction<string>) {
state.message = payload
}
}
})
export const { changeMessageAction } = counterSlice.actions
export default counterSlice.reducer
到了這里,關(guān)于react+ts【項(xiàng)目實(shí)戰(zhàn)一】配置項(xiàng)目/路由/redux的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!