您现在的位置是:首页 >技术教程 >react18+ts4的项目初始化(带路由配置,ReduxToolkit,axios封装)(附github地址)网站首页技术教程
react18+ts4的项目初始化(带路由配置,ReduxToolkit,axios封装)(附github地址)
1.项目的初始化配置
(1)创建react+ts项目
create-react-app react_ts_music --template typescript
生成目录:
修改运行package.json中的配置
改成:
(2)项目基本配置
1.craco配置webpack
npm install @craco/craco@alpha -D
创建craco.config.json文件:
const path = require('path')
const resolve = (dir) => path.resolve(__dirname, dir)
module.exports = {
webpack: {
alias: {
'@': resolve('src'),
}
}
}
2.配置@指定的src文件下的内容
在tsconfig.json文件中加:
3.代码规范配置
创建.editorconfig文件配置
# http://editorconfig.org
root = true
[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为 utf-8
indent_style = space # 缩进风格(tab | space)
indent_size = 2 # 缩进大小
end_of_line = lf # 控制换行类型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行尾的任意空白字符
insert_final_newline = true # 始终在文件末尾插入一个新行
[*.md] # 表示仅 md 文件适用以下规则
max_line_length = off
trim_trailing_whitespace = false
配置prettier工具
可让代码更加好看,风格一致
npm install prettier -D
创建_prettierrc文件
{
"useTabs": false,
"tabWidth": 2,
"printWidth": 80,
"singleQuote": false,
"trailingComma": "none",
"semi": false
}
改运行文件
# /build/*
# .local
# .output.js
# /node_modules/**
# **/*.svg
# **/*.sh
# /public/*
配置ESlint工具
npm install eslint -D
初始化:
npx eslint --init
module.exports = {
env: {
browser: true,
node: true,
es2021: true
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:@typescript-eslint/recommended',
],
overrides: [],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
plugins: ['react', '@typescript-eslint'],
rules: {
'@typescript-eslint/no-var-requires': 'off',
}
}
2.项目内容配置
(1)目录的划分
(2)配置less
npm install craco-less@2.1.0-alpha.0
craco.config.js文件:
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)路由的配置
npm install react-router-dom
router文件夹建立index.tsx
import React,{lazy} from "react";
import {Navigate} from 'react-router-dom'
import {RouteObject} from 'react-router-dom'
const Discover = lazy(() => import('@/views/discover'))
const routes: RouteObject[]=[
{
path: '/',
element: <Navigate to="/discover" />
},
{
path: '/discover',
element: <Discover />,
children: [
]
},
]
export default routes
import React,{Suspense} from "react";
import {useRoutes,Link} from 'react-router-dom'
import routes from './router'
function App() {
return <div className="App">
<div className="topHeader">
<Link to="/discover">discover</Link>
<Link to="/users">users</Link>
<Link to="/task">task</Link>
</div>
<Suspense fallback="Loading...">
<div className="main">
{useRoutes(routes)}
</div>
</Suspense>
</div>;
}
export default App;
import React from "react";
import ReactDOM from "react-dom/client";
import {BrowserRouter} from 'react-router-dom'
import App from "@/App";
import "@/assets/css/base.less"
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(<BrowserRouter><App /></BrowserRouter>);
(4)redux的配置
安装react-redux:react-redux和@reduxjs/toolkit。
npm install react-redux @reduxjs/toolkit -S
安装完相关包以后开始编写基本的 RTK 程序
- 创建一个store文件夹
- 创建一个index.ts做为主入口
- 创建一个festures文件夹用来装所有的store
- 创建一个counterSlice.js文件,并导出简单的加减方法
创建 Redux State Slice
创建 slice 需要一个字符串名称来标识切片、一个初始 state 以及一个或多个定义了该如何更新 state 的 reducer 函数。slice 创建后 ,我们可以导出 slice 中生成的 Redux action creators 和 reducer 函数。
store/features/counterSlice.js
import { createSlice } from '@reduxjs/toolkit'
// 创建一个Slice
export const counterSlice = createSlice({
name: 'counter',
initialState:{
value: 0,
},
reducers: {
// 定义一个加的方法
increment:(state,{payload}) => {
state.value += parseInt(payload)
},
// 定义一个减的方法
decrement: state => {
state.value -= 1
},
},
})
// 导出加减方法
export const { increment, decrement } = counterSlice.actions
// 暴露reducer
export default counterSlice.reducer
createSlice是一个全自动的创建reducer切片的方法,在它的内部调用就是createAction和createReducer,之所以先介绍那两个也是这个原因。createSlice需要一个对象作为参数,对象中通过不同的属性来指定reducer的配置信息。
createSlice(configuration object)
配置对象中的属性:
- name —— reducer的名字,会作为action中type属性的前缀,不要重复
- initialState —— state的初始值
- reducers —— reducer的具体方法,需要一个对象作为参数,可以以方法的形式添加reducer,RTK会自动生成action对象。
总的来说,使用createSlice创建切片后,切片会自动根据配置对象生成action和reducer,action需要导出给调用处,调用处可以使用action作为dispatch的参数触发state的修改。reducer需要传递给configureStore以使其在仓库中生效。
我们可以看看counterSlice和counterSlice.actions是什么样子
将 Slice Reducers 添加到 Store 中
下一步,我们需要从计数切片中引入 reducer 函数,并将它添加到我们的 store 中。通过在 reducer 参数中定义一个字段,我们告诉 store 使用这个 slice reducer 函数来处理对该状态的所有更新。
我们以前直接用redux是这样的
const reducer = combineReducers({
counter:counterReducers
});
const store = createStore(reducer);
store/index.js
切片的reducer属性是切片根据我们传递的方法自动创建生成的reducer,需要将其作为reducer传递进configureStore的配置对象中以使其生效:
import { configureStore } from '@reduxjs/toolkit'
import { useSelector,useDispatch } from 'react-redux'
import type { TypedUseSelectorHook } from 'react-redux'
import counterSlice from './features/counterSlice'
// configureStore创建一个redux数据
const store = configureStore({
// 合并多个Slice
reducer: {
counter: counterSlice,
},
})
//app的hook,ts类型定义
export type IRootState=ReturnType<typeof store.getState>
type AppDispatch = typeof store.dispatch
export const useAppSelector: TypedUseSelectorHook<IRootState> = useSelector
export const useAppDispatch: () => AppDispatch = useDispatch
export default store
- configureStore需要一个对象作为参数,在这个对象中可以通过不同的属性来对store进行设置,比如:reducer属性用来设置store中关联到的reducer,preloadedState用来指定state的初始值等,还有一些值我们会放到后边讲解。
- reducer属性可以直接传递一个reducer,也可以传递一个对象作为值。如果只传递一个reducer,则意味着store中只有一个reducer。若传递一个对象作为参数,对象的每个属性都可以执行一个reducer,在方法内部它会自动对这些reducer进行合并。
store加到全局
main.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
// redux toolkit
import { Provider } from 'react-redux'
import store from './store'
ReactDOM.createRoot(document.getElementById('root')).render(
<Provider store={store}>
<App />
</Provider>,
)
在 React 组件中使用 Redux 状态和操作
现在我们可以使用 React-Redux 钩子让 React 组件与 Redux store 交互。我们可以使用 useSelector 从 store 中读取数据,使用 useDispatch dispatch actions。
App.jsx
import React, { memo ,useRef,createRef} from 'react'
import { increment, decrement } from '@/store/features/counterSlice'
import type { FC, ReactNode } from 'react'
// import { IRootState } from '@/store'
import { useAppSelector ,useAppDispatch} from '@/store'
interface IProps {
children?: ReactNode
}
const Artist: FC<IProps> = () => {
const count = useAppSelector((state) => state.counter.value)
const dispatch = useAppDispatch()
const InputRef= useRef<HTMLInputElement>(null);
const InputRef2= createRef();
return (
<div>
<h1>count</h1>
<div style={{ width: 100, margin: '10px' }}>
<input ref = { InputRef } type="text"/>
<button onClick={() => dispatch(increment(InputRef.current?.value))}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
<span>{count}</span>
</div>
</div>
)
}
export default memo(Artist)
现在,每当你点击”递增“和“递减”按钮。
- 会 dispatch 对应的 Redux action 到 store
- 在计数器切片对应的 reducer 中将看到 action 并更新其状态
- 组件将从 store 中看到新的状态,并使用新数据重新渲染组件
(5)axios配置
在service文件下:创建requset文件夹:
service/requset:
配置接口的路径:
const BASE_URL = 'http://codercba.com:9002/'
const TIME_OUT = 10000
console.log(process.env.REACT_APP_NAME)
console.log(process.env.REACT_APP_AGE)
export { BASE_URL, TIME_OUT }
拦截器类型:
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
export interface HYRequestInterceptors<T = AxiosResponse> {
requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig
requestInterceptorCatch?: (error: any) => any
responseInterceptor?: (res: T) => T
responseInterceptorCatch?: (error: any) => any
}
export interface HYRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
interceptors?: HYRequestInterceptors<T>
showLoading?: boolean
}
请求类的封装:
import axios from 'axios'
import type { AxiosInstance } from 'axios'
import type { HYRequestInterceptors, HYRequestConfig } from './type'
const DEAFULT_LOADING = true
class HYRequest {
instance: AxiosInstance
interceptors?: HYRequestInterceptors
showLoading: boolean
constructor(config: HYRequestConfig) {
// 创建axios实例
this.instance = axios.create(config)
// 保存基本信息
this.showLoading = config.showLoading ?? DEAFULT_LOADING
this.interceptors = config.interceptors
// 使用拦截器
// 1.从config中取出的拦截器是对应的实例的拦截器
this.instance.interceptors.request.use(
// this.interceptors?.requestInterceptor,
this.interceptors?.requestInterceptorCatch
)
this.instance.interceptors.response.use(
this.interceptors?.responseInterceptor,
this.interceptors?.responseInterceptorCatch
)
// 2.添加所有的实例都有的拦截器
this.instance.interceptors.request.use(
(config) => {
return config
},
(err) => {
return err
}
)
this.instance.interceptors.response.use(
(res) => {
const data = res.data
if (data.returnCode === '-1001') {
console.log('请求失败~, 错误信息')
} else {
return data
}
},
(err) => {
// 例子: 判断不同的HttpErrorCode显示不同的错误信息
if (err.response.status === 404) {
console.log('404的错误~')
}
return err
}
)
}
request<T = any>(config: HYRequestConfig<T>): Promise<T> {
return new Promise((resolve, reject) => {
// 1.单个请求对请求config的处理
if (config.interceptors?.requestInterceptor) {
config = config.interceptors.requestInterceptor(config)
}
// 2.判断是否需要显示loading
if (config.showLoading === false) {
this.showLoading = config.showLoading
}
this.instance
.request<any, T>(config)
.then((res) => {
// 1.单个请求对数据的处理
if (config.interceptors?.responseInterceptor) {
res = config.interceptors.responseInterceptor(res)
}
// 2.将showLoading设置true, 这样不会影响下一个请求
this.showLoading = DEAFULT_LOADING
// 3.将结果resolve返回出去
resolve(res)
})
.catch((err) => {
// 将showLoading设置true, 这样不会影响下一个请求
this.showLoading = DEAFULT_LOADING
reject(err)
return err
})
})
}
get<T = any>(config: HYRequestConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'GET' })
}
post<T = any>(config: HYRequestConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'POST' })
}
delete<T = any>(config: HYRequestConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'DELETE' })
}
patch<T = any>(config: HYRequestConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'PATCH' })
}
}
export default HYRequest
service/modules对要用的接口进行封装:
import hyRequest from '..'
export function getTopBanner() {
return hyRequest.get({
url: '/banner'
})
}
export function getHotRecommend() {
return hyRequest.get({
url: '/personalized'
})
}
export function getNewAlbum(offset = 0, limit = 10) {
return hyRequest.get({
url: '/album/new',
params: {
offset,
limit
}
})
}
export function getPlayListDetail(id: number) {
return hyRequest.get({
url: '/playlist/detail',
params: {
id
}
})
}
service/index
请求函数的封装
// service统一出口
import HYRequest from './request'
import { BASE_URL, TIME_OUT } from './request/config'
const hyRequest = new HYRequest({
baseURL: BASE_URL,
timeout: TIME_OUT,
interceptors: {
requestInterceptor: (config) => {
return config
},
requestInterceptorCatch: (err) => {
return err
},
responseInterceptor: (res) => {
return res
},
responseInterceptorCatch: (err) => {
return err
}
}
})
export default hyRequest
路由的请求:
import hyRequest from '@/service';
import React, { memo,useEffect,useState} from 'react'
import type { FC, ReactNode } from 'react'
import {getTopBanner} from '@/service/modules/recommend'
interface IProps {
children?: ReactNode
}
interface listRoot {
imageUrl: string
targetId: number
adid: any
targetType: number
titleColor: string
typeTitle: string
url: any
exclusive: boolean
monitorImpress: any
monitorClick: any
monitorType: any
monitorImpressList: any
monitorClickList: any
monitorBlackList: any
extMonitor: any
extMonitorInfo: any
adSource: any
adLocation: any
adDispatchJson: any
encodeId: string
program: any
event: any
video: any
song: any
scm: string
bannerBizType: string
}
const AxiosTemplate: FC<IProps> = () => {
const [list , setList]=useState<listRoot[]>([])
useEffect(() =>{
(async function() {
const res= await getTopBanner()
setList(res.banners)
})()
},[])
return <div>
<h1>我是axios请求的数据:</h1>
<ul>
{
list.map((item)=>{
return (
<li key={item.encodeId}>{item.imageUrl}</li>
)
})
}
</ul>
</div>
}
export default memo(AxiosTemplate)