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')constresolve=(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')constresolve=(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'importtype{ AxiosInstance }from'axios'importtype{ HYRequestInterceptors, HYRequestConfig }from'./type'constDEAFULT_LOADING=trueclassHYRequest{
instance: AxiosInstance
interceptors?: HYRequestInterceptors
showLoading:booleanconstructor(config: HYRequestConfig){// 创建axios实例this.instance = axios.create(config)// 保存基本信息this.showLoading = config.showLoading ??DEAFULT_LOADINGthis.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>{returnnewPromise((resolve, reject)=>{// 1.单个请求对请求config的处理if(config.interceptors?.requestInterceptor){
config = config.interceptors.requestInterceptor(config)}// 2.判断是否需要显示loadingif(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_LOADINGreject(err)return err
})})}get<T=any>(config: HYRequestConfig<T>):Promise<T>{returnthis.request<T>({...config, method:'GET'})}post<T=any>(config: HYRequestConfig<T>):Promise<T>{returnthis.request<T>({...config, method:'POST'})}delete<T=any>(config: HYRequestConfig<T>):Promise<T>{returnthis.request<T>({...config, method:'DELETE'})}patch<T=any>(config: HYRequestConfig<T>):Promise<T>{returnthis.request<T>({...config, method:'PATCH'})}}exportdefault HYRequest
service/modules对要用的接口进行封装:
import hyRequest from'..'exportfunctiongetTopBanner(){return hyRequest.get({
url:'/banner'})}exportfunctiongetHotRecommend(){return hyRequest.get({
url:'/personalized'})}exportfunctiongetNewAlbum(offset =0, limit =10){return hyRequest.get({
url:'/album/new',
params:{
offset,
limit
}})}exportfunctiongetPlayListDetail(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 =newHYRequest({
baseURL:BASE_URL,
timeout:TIME_OUT,
interceptors:{requestInterceptor:(config)=>{return config
},requestInterceptorCatch:(err)=>{return err
},responseInterceptor:(res)=>{return res
},responseInterceptorCatch:(err)=>{return err
}}})exportdefault 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)
3.github代码地址:
版权归原作者 阿泽不会飞 所有, 如有侵权,请联系我们删除。