网易云音乐项目(React+Ts) 二

58 阅读4分钟

一、react+ts项目路由配置 先安装路由 npm i react-router-dom image.png "发现音乐"、"我的音乐"、"关注"、"下载客户端"属于App.tsx下的一级路由 "商城"、"音乐人"、"云推歌"属于超链接跳转 "发现音乐"下的推荐 排行榜 歌单 博客 歌手 新碟上架属于二级路由 在router/index.tsx中配置路由规则 为了项目优化,对路由进行懒加载处理(使用lazy和suspense)

image.png

image.png

image.png 并在views文件夹下创建每一个路由组件

image.png image.png 并在discover组件下新建文件夹c-views用于存放discover组件下的二级路由组件 再到项目路口文件中引入路由

image.png

image.png 这里用到的是表示在某某组件下默认跳转到另一个组件显示

image.png 表示一打开项目,就默认跳转到"发现音乐"组件显示

image.png 表示在"发现音乐"组件中,默认显示"推荐"组件的内容 最后显示路由,在App.tsx组件中,

image.png 利用useRoutes(routes),作用是根据当前页面的路径去路由规则中找到匹配的路由组件渲染出来显示。放到suspense组件中目的是当路由组件暂时未被加载出来时,显示suspense组件中的fallback后的内容。 二、React+Ts项目:组件和props的类型约束 目的:为了限制传入当前组件中的数据/内容数据类型

image.png 方式: 1、先定义props的接口类型 interface Iprops { children?:ReactNode // ReactNode表示可以识别传入的任意类型的组件内容 } 2、const 组件名 :FC=()=>{ } // FC<接口类型>是固定语法,用于限制组件中可以传入的数据的类型。 3、最后导出当前组件 export default 组件名 4、利用memo函数,可以限制当父组件重新渲染时,子组件不跟着一起重新渲染 (即若当前组件中有内容传入时,父组件重新渲染时,当前组件只要props数据未发生改变时,当前组件就不会跟着重新渲染) 5、生成代码片段 由于每一个组件中都需要配置组件和props的类型约束,所以可以将这部分代码生成一个代码片段,便于复用。 打开snippet generator网站 将复用的代码输入,填入命名,点击"Copy snippet" 复制左边生成的代码片段,打开vscode->文件->首选项->配置用户代码片段->选择typescriptReact文件->粘贴先前复制的内容 这样之后在组件中,只要输入 命名的题目+回车键就可以生成固定代码了

三、二级路由配置 "发现音乐"组件下的 推荐 排行榜 歌单等属于二级路由组件。 在discover组件下新建c-views文件夹专门用于存放二级路由组件。 配置二级路由出口位置在"发现组件"下。

image.png 四、redux配置(用于管理数据) 下载redux: npm install @reduxjs/toolkit react-redux 在项目入口文件中引入store

image.png image.png 五、配置typescript环境下的redux 发现在组件中获取redux中的数据,需要传入数据的泛型限制。限制获取到的数据类型。(可以在redux官网中找到配置方法。) 方法:在src/store/index.tsx中配置:

image.png 配置好了之后,在组件中想要获取redux中存储的数据时,就用useAppSelector代替useSelector ; 在组件中想要调用redux中存储的修改数据的方法时,就用useAppDispatch代替useDispatch。 六、利用redux来管理组件中用到的数据时,可以分模块管理。 例如:store/modules/Counter.ts 然后再引入模块到store/index.tsx中

在redux中管理数据时,需要对初始状态数据也做一个泛型限制,以及对传入方法中的参数也做一个泛型限制。
eg:
1、定义一个泛型接口
2、在创建初始状态数据变量时,传入泛型,再声明数据初始值。

image.png 同样也要对传入方法中的参数做类型限制

{payload}:PayloadAction<传入的参数类型>
eg:

interface IState {
    foodsList:any[] //表示该数据是一个任意类型的数组
}
const initialState : IState ={
    foodsList:[]
}
const foodsStore=createSlice({
    name:'foods',
    initialState,
    reducers:{}
})

七、二次封装axios

image.png

image.png

request/index.ts中:

import axios from 'axios'

import type { AxiosInstance } from 'axios' import type { HYRequestConfig } from './type'

// 拦截器: 蒙版Loading/token/修改配置

/**

  • 两个难点:
  • 1.拦截器进行精细控制
  • 全局拦截器

  • 实例拦截器

  • 单次请求拦截器

  • 2.响应结果的类型处理(泛型) */

class HYRequest { instance: AxiosInstance

// request实例 => axios的实例 constructor(config: HYRequestConfig) { this.instance = axios.create(config)

// 每个instance实例都添加拦截器
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
  }
)

// 针对特定的hyRequest实例添加拦截器
this.instance.interceptors.request.use(
  config.interceptors?.requestSuccessFn,
  config.interceptors?.requestFailureFn
)
this.instance.interceptors.response.use(
  config.interceptors?.responseSuccessFn,
  config.interceptors?.responseFailureFn
)

}

// 封装网络请求的方法 // T => IHomeData request<T = any>(config: HYRequestConfig) { // 单次请求的成功拦截处理 if (config.interceptors?.requestSuccessFn) { config = config.interceptors.requestSuccessFn(config) }

// 返回Promise
return new Promise<T>((resolve, reject) => {
  this.instance
    .request<any, T>(config)
    .then((res) => {
      // 单词响应的成功拦截处理
      if (config.interceptors?.responseSuccessFn) {
        res = config.interceptors.responseSuccessFn(res)
      }
      resolve(res)
    })
    .catch((err) => {
      reject(err)
    })
})

}

get<T = any>(config: HYRequestConfig) { return this.request({ ...config, method: 'GET' }) } post<T = any>(config: HYRequestConfig) { return this.request({ ...config, method: 'POST' }) } delete<T = any>(config: HYRequestConfig) { return this.request({ ...config, method: 'DELETE' }) } patch<T = any>(config: HYRequestConfig) { return this.request({ ...config, method: 'PATCH' }) } }

export default HYRequest

image.png

image.png