1. Initial configuration of the project
(1) Create a react + ts project
create-react-app react_ts_music –template typescript
Build directory:
Modify the configuration in running package.json
Change to:
(2) Basic project configuration
1. Craco configuration webpack
npm install @craco/craco@alpha -D
Create the craco.config.json file:
const path = require('path') const resolve = (dir) => path. resolve(__dirname, dir) module.exports = {<!-- --> webpack: {<!-- --> alias: {<!-- --> '@': resolve('src'), } } }
2. Configure the content under the src file specified by @
Add to the tsconfig.json file:
3. Code specification configuration
Create .editorconfig file configuration
# http://editorconfig.org root = true [*] # means all files apply charset = utf-8 # set the file character set to utf-8 indent_style = space # indentation style (tab | space) indent_size = 2 # indentation size end_of_line = lf # Control line break type (lf | cr | crlf) trim_trailing_whitespace = true # Remove any whitespace characters at the end of the line insert_final_newline = true # always insert a newline at the end of the file [*.md] # means only md files apply the following rules max_line_length = off trim_trailing_whitespace = false
Configure prettier tool
It can make the code more beautiful and consistent in style
npm install prettier -D
Create_prettierrc file
{<!-- --> "useTabs": false, "tabWidth": 2, "printWidth": 80, "singleQuote": false, "trailingComma": "none", "semi": false }
Change the run file
& height=202 & amp;id=u711e59d3 & amp;originHeight=303 & amp;originWidth=644 & amp;originalType=binary & amp;ratio=1.5 & amp;rotation=0 & amp;showTitle=false & amp;size= 32050 & amp;status=done & amp;style=none & amp;taskId=u281520a2-b024-48bd-bc52-3f2defc82e0 & amp;title= & amp;width=429.3333333333333″ alt=”image.png”>
# /build/* #.local #.output.js # /node_modules/** # **/*.svg # **/*.sh # /public/*
Configure ESlint tool
npm install eslint -D
initialization:
npx eslint –init
& height=241 & amp;id=u43c0210f & amp;originHeight=362 & amp;originWidth=1111 & amp;originalType=binary & amp;ratio=1.5 & amp;rotation=0 & amp;showTitle=false & amp;size= 74476 & amp;status=done & amp;style=none & amp;taskId=u2b16a564-03a5-4a70-a5a5-e75425c7114 & amp;title= & amp;width=740.6666666666666″ alt=”image.png”>
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. Project content configuration
(1) Division of directories
(2) Configure less
npm install [email protected]
craco.config.js file:
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) Routing configuration
npm install react-router-dom
Create index.tsx in the router folder
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 configuration
Install react-redux: react-redux and @reduxjs/toolkit.
npm install react-redux @reduxjs/toolkit -S
Start writing basic RTK programs after installing the relevant packages
- Create a store folder
- Create an index.ts as the main entry
- Create a festivals folder to hold all stores
- Create a counterSlice.js file and export simple addition and subtraction methods
Creating a Redux State Slice
Creating a slice requires a string name to identify the slice, an initial state, and one or more reducer functions that define how the state should be updated. After the slice is created, we can export the Redux action creators and reducer functions generated in the slice.
store/features/counterSlice.js
import { createSlice } from '@reduxjs/toolkit' // Create a Slice export const counterSlice = createSlice({ name: 'counter', initialState: { value: 0, }, reducers: { // define an add method increment:(state,{payload}) => { state.value += parseInt(payload) }, // define a subtraction method decrement: state => { state.value -= 1 }, }, }) // Export addition and subtraction methods export const { increment, decrement } = counterSlice. actions // expose reducers export default counterSlice.reducer
createSlice is a fully automatic method for creating reducer slices. Its internal calls are createAction and createReducer. This is the reason why those two are introduced first. createSlice requires an object as a parameter, and the configuration information of the reducer is specified in the object through different attributes.
createSlice(configuration object)
Properties in the configuration object:
- name – the name of the reducer, which will be used as the prefix of the type attribute in the action, do not repeat
- initialState – the initial value of the state
- reducers — the specific method of the reducer, which requires an object as a parameter, and the reducer can be added in the form of a method, and RTK will automatically generate an action object.
In general, after using createSlice to create a slice, the slice will automatically generate an action and a reducer based on the configuration object. The action needs to be exported to the calling site, and the calling site can use the action as a dispatch parameter to trigger the state modification. The reducer needs to be passed to configureStore for it to take effect in the repository.
We can see what counterSlice and counterSlice.actions look like
Add Slice Reducers to Store
Next, we need to import the reducer function from the count slice and add it to our store. By defining a field in the reducer parameter, we tell the store to use this slice reducer function for all updates to the state.
We used to use redux directly like this
const reducer = combineReducers({ counter:counterReducers }); const store = createStore(reducer);
store/index.js
The reducer attribute of the slice is the reducer automatically created by the slice according to the method we passed. It needs to be passed as a reducer into the configuration object of configureStore to make it effective:
import { configureStore } from '@reduxjs/toolkit' import { useSelector,useDispatch } from 'react-redux' import type { TypedUseSelectorHook } from 'react-redux' import counterSlice from './features/counterSlice' // configureStore creates a redux data const store = configureStore({ // Merge multiple Slices reducer: { counter: counterSlice, }, }) //app's hook, ts type definition 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 needs an object as a parameter, in which different attributes can be used to set the store, for example: reducer attribute is used to set the reducer associated with the store, preloadedState is used to specify the initial value of the state, etc., and some other values We will explain it later.
- The reducer attribute can pass a reducer directly, or an object as a value. If only one reducer is passed, it means there is only one reducer in the store. If you pass an object as a parameter, each property of the object can execute a reducer, and it will automatically merge these reducers inside the method.
store added to the global
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>, )
Using Redux state and actions in React components
Now we can use React-Redux hooks to make React components interact with the Redux store. We can use useSelector to read data from the store and use useDispatch to 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)
Now, whenever you click the “increment” and “decrement” buttons.
- Will dispatch the corresponding Redux action to the store
- The reducer corresponding to the counter slice will see the action and update its state
- The component will see the new state from the store and re-render the component with the new data
(5) axios configuration
Under the service file: Create the requset folder:
service/request:
Path to configure interface:
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 }
Interceptor type:
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 }
Encapsulation of the request class:
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) {<!-- --> // create axios instance this.instance = axios.create(config) // save basic information this.showLoading = config.showLoading DEAFULT_LOADING this.interceptors = config.interceptors // use interceptor // 1. The interceptor taken from config is the interceptor of the corresponding instance this.instance.interceptors.request.use( // this.interceptors?.requestInterceptor, this.interceptors?.requestInterceptorCatch ) this.instance.interceptors.response.use( this.interceptors?.responseInterceptor, this.interceptors?.responseInterceptorCatch ) // 2. Add interceptors that all instances have 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('request failed~, error message') } else {<!-- --> return data } }, (err) => {<!-- --> // Example: Judging different HttpErrorCode to display different error messages if (err. response. status === 404) {<!-- --> console.log('404 error~') } return err } ) } request<T = any>(config: HYRequestConfig<T>): Promise<T> {<!-- --> return new Promise((resolve, reject) => {<!-- --> // 1. Single request processing of request config if (config.interceptors?.requestInterceptor) {<!-- --> config = config.interceptors.requestInterceptor(config) } // 2. Determine whether loading needs to be displayed if (config. showLoading === false) {<!-- --> this.showLoading = config.showLoading } this. instance .request<any, T>(config) .then((res) => {<!-- --> // 1. Processing of data by a single request if (config.interceptors?.responseInterceptor) {<!-- --> res = config.interceptors.responseInterceptor(res) } // 2. Set showLoading to true, so that it will not affect the next request this.showLoading = DEAFULT_LOADING // 3. Return the result resolve resolve(res) }) .catch((err) => {<!-- --> // Set showLoading to true so that it will not affect the next request 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 encapsulates the interface to be used:
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
Encapsulation of request functions
// service unified export 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
Routed request:
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 monitor Impress: 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>I am the data requested by axios:</h1> <ul> { list. map((item) => { return ( <li key={item.encodeId}>{item.imageUrl}</li> ) }) } </ul> </div> } export default memo(AxiosTemplate)
3.github code address:
https://github.com/wzz778/react_ts_template