# react-music(hooks) **Repository Path**: huangxiaolun/react-music-hooks ## Basic Information - **Project Name**: react-music(hooks) - **Description**: 用hooks构建的react项目,具有一定的参考性 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-02-23 - **Last Updated**: 2021-03-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 安装项目依赖: ```react yarn install ``` 项目运行: ```react yarn start ``` 知识点参考:coderwhy公众号 react知识点栏目 ## 项目规范 **项目规范:项目中有一些开发规范和代码风格** - 1.文件夹、文件名称统一小写、多个单词以连接符(-)连接; - 2.JavaScript变量名称采用小驼峰标识,常量全部使用大写字母,组件采用大驼峰; - 3.CSS采用普通CSS和styled-component结合来编写(全局采用普通CSS、局部采用styled-component); - 4.整个项目不再使用class组件,统一使用函数式组件,并且全面使用Hooks; - 5.所有的函数式组件,为了避免不必要的渲染,全部使用memo进行包裹; - 6.组件内部的状态,使用useState、useReducer;业务数据全部放在redux中管理; - 7.函数组件内部基本按照如下顺序编写代码: - - 组件内部state管理; - redux的hooks代码; - 其他组件hooks代码; - 其他逻辑代码; - 返回JSX代码; - 8.redux代码规范如下: - - redux结合ImmutableJS - 每个模块有自己独立的reducer,通过combineReducer进行合并; - 异步请求代码使用redux-thunk,并且写在actionCreators中; - redux直接采用redux hooks方式编写,不再使用connect; - 9.网络请求采用axios - - 对axios进行二次封装; - 所有的模块请求会放到一个请求文件中单独管理; - 10.项目使用AntDesign - - 项目中某些AntDesign中的组件会被拿过来使用; - 但是多部分组件还是自己进行编写; - 其他规范在项目中根据实际情况决定和编写; ps : js和jsx结尾一样 很多大型项目还是用js结尾 ## 项目准备 ### 文件夹 image-20210224093943974 ### css ```webpack yarn add normalize.css -S ``` 样式重置-*全局样式* --如果要用到css变量(scss less)就需要在webpack里面去配置css了 -另一个项目有案例 ### 配置别名-扩展webpack ```webpack //想要修改webpack配置,但不使用yarn eject(也不推荐暴露) yarn add @craco/craco ``` ![image-20210224100046591](readmeImg/image-20210224100046591.png) ![image-20210224101637307](readmeImg/image-20210224100615091.png) ### 项目页面分析 ![image-20210224101908298](readmeImg/image-20210224101908298.png) > 构建解构 #### 配置路由 ```route yarn add react-router-dom yarn add react-router-config //管理路由 ``` ![image-20210224105627357](readmeImg/image-20210224105627357.png) ![image-20210225161359401](readmeImg/image-20210225161359401.png) ## 知识点 > 问题一:采用什么css方式 传统方式:行内样式和类样式 react组件化开发,避免css污染 --解决办法: 1.**css-in-js** 在js文件中写css,样式文件的后缀名为 .js,这种方式最大的优点就是可以传递参数。(我不是很喜欢这种 因为js和css没有分离) 2.模块化样式,样式文件的后缀为.module.css,使用的时候以对象的形式来调用样式 (这种也太麻烦了) 3.利用sass或者less (就是在传统方式上,只不过我们要注意的是,就是每一个组件样式最外层需要一个 独一的样式名,这样就不会出现问题了啥)---**这是我比较能接受的** ```webpack ``` > classNames 动态切换样式,通过 classNames 插件来完成,通过对样式属性设置 true 或者 false,来设置是否将该样式应用到DOM对象中。 ps:原本这个项目用的css-in-js 样式 但是 我没有我通过scss方式来做 > 第一次配置还是有点麻烦 ![image-20210224134617906](readmeImg/image-20210224134617906.png) ![image-20210224134707157](readmeImg/image-20210224134707157.png) ![image-20210224134748401](readmeImg/image-20210224134748401.png) 其它的直接引就是 ## 二级路由 ![image-20210224180931325](readmeImg/image-20210224180931325.png) ## 配置axios ```axios /**axios封装 * 请求拦截、相应拦截、错误统一处理 */ import axios from "axios"; // import { getToken, getSession } from "../utils/sesstion"; import { message } from "antd"; // import QS from "qs"; // 环境的切换--需要改 const BASEURL = process.env.NODE_ENV === "production" ? process.env.REACT_APP_API : process.env.REACT_APP_API; axios.defaults.baseURL = BASEURL; // 请求超时时间 axios.defaults.timeout = 15000; // post请求头 axios.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded;charset=UTF-8"; // 请求拦截器 axios.interceptors.request.use( (config) => { // 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了 // 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断 // const token = getToken(), // username = getSession("Username"); // token && (config.headers["Token"] = token); // username && (config.headers["Username"] = username); // config.headers.UserName = user.username return config; }, (error) => { return Promise.error(error); } ); // 响应拦截器 axios.interceptors.response.use( (response) => { if (response.status === 200) { // 在验证后端给的code if (response.data.resCode !== 0) { message.error(response.data.message); return Promise.reject(response); } return Promise.resolve(response); } else { return Promise.reject(response); } }, // 服务器状态码不是200的情况 (error) => { if (error.response.status) { switch (error.response.status) { // 401: 未登录 // 未登录则跳转登录页面,并携带当前页面的路径 // 在登录成功后返回当前页面,这一步需要在登录页操作。 case 401: // router.replace({ // path: '/login', // query: { redirect: router.currentRoute.fullPath } // }); break; // 403 token过期 // 登录过期对用户进行提示 // 清除本地token和清空vuex中token对象 // 跳转登录页面 case 403: message.error("登录过期,请重新登录"); // Toast({ // message: '登录过期,请重新登录', // duration: 1000, // forbidClick: true // }); // // 清除token // localStorage.removeItem('token'); // store.commit('loginSuccess', null); // // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面 // setTimeout(() => { // router.replace({ // path: '/login', // query: { // redirect: router.currentRoute.fullPath // } // }); // }, 1000); break; // 404请求不存在 case 404: message.error("网络请求不存在"); // Toast({ // message: '网络请求不存在', // duration: 1500, // forbidClick: true // }); break; // 其他错误,直接抛出错误提示 default: message.error(error.response.data.message); // Toast({ // message: error.response.data.message, // duration: 1500, // forbidClick: true // }); } return Promise.reject(error.response); } } ); /** * get方法,对应get请求 * @param {String} url [请求的url地址] * @param {Object} params [请求时携带的参数] */ function get(url, params = {}) { return new Promise((resolve, reject) => { axios .get(url, { params: params, }) .then((res) => { resolve(res.data); }) .catch((err) => { reject(err.data); }); }); } /** * post方法,对应post请求 * @param {String} url [请求的url地址] * @param {Object} params [请求时携带的参数] */ function post(url, params = {}) { return new Promise((resolve, reject) => { // QS.stringify(params) -->这里邮箱@ 变成了百分号 axios .post(url, params) .then((res) => { resolve(res.data); }) .catch((err) => { reject(err.data); }); }); } export { get, post }; ``` ## redux react-redux 就是这一套:配置还是不变 ![image-20210225130204540](readmeImg/image-20210225130204540.png) ![image-20210225130333206](readmeImg/image-20210225130333206.png) ```react //app.js import { Provider } from "react-redux"; import store from "store"; xx ``` > 使用 : 方式一 ![image-20210225131042255](readmeImg/image-20210225131042255.png) ![image-20210225131619968](readmeImg/image-20210225131619968.png) > 方式二 我常使用的 ![image-20210225134413513](readmeImg/image-20210225134413513.png) > 使用hooks useDispatch useSelector `useSelector 有弊端 性能问题`:他是通过全等来比较useSelector第一个参数返回的对象,不是进行浅层比较的,所以会重新渲染 > shallowEqual浅层比较 所以,useSelector需要传入第二个参数 shallowEqual -(只有当我们需要每次都渲染的时候就可以不用传入这个参数) ![image-20210225142645103](readmeImg/image-20210225142645103.png) ```react import React , {memo,useEffect} from 'react' // import { connect } from "react-redux"; import { useDispatch,useSelector,shallowEqual } from "react-redux"; // import actions from "store/action/index.js"; import recommend from "store/action/recommend.js"; function WYAlbum(props) { const dispatch = useDispatch(); const state =useSelector(state=>({...state.recommend}),shallowEqual) useEffect(() => { dispatch(recommend.getTopBanner()) }, [dispatch]) return (
) } export default memo(WYAlbum) // export default connect(state=>({...state.recommend}),actions.recommend)(memo(WYAlbum)) ``` > redux性能问题 === (对象拷贝问题) ![image-20210225143555963](readmeImg/image-20210225143555963.png) ![image-20210225143604097](readmeImg/image-20210225143604097.png) `解决办法:ImmutableJS `这也是javascript目前最优的解决方案 优点:尽可能使用之前的数据结构,生成新的对象 ![image-20210225143709754](readmeImg/image-20210225143709754.png) `具体api看文档` > react中使用 ```react yarn add immutable ``` `修改地方一` ![image-20210225150741560](readmeImg/image-20210225150741560.png) `修改地方二` ```react yarn add redux-immutable ``` ![image-20210225152608713](readmeImg/image-20210225152608713.png) `修改地方三` 修改三就是根据修改一和修改二 所决定的: 也就是获取的时候要用immutable的api方式获取 ![image-20210225153113913](readmeImg/image-20210225153113913.png) ![image-20210225161556451](readmeImg/image-20210225161556451.png) > 组件传参校验 ```react yarn add prop-types import PropTypes from 'prop-types'; xx.propTypes={} ``` > useCallback在我们需要 将函数传入自定义组件的时候 调用 > 歌曲逻辑 ![image-20210301165254458](readmeImg/image-20210301165254458.png) > 歌词逻辑 请求到所有歌词数据,然后处理成数组[{'毫秒':'歌词'}] 通过毫秒(毫秒小于当前时间得到的索引减一 就是当前歌词) 也是根据这个索引 确定是否高亮 位置等 > 懒加载 ![image-20210302105954373](readmeImg/image-20210302105954373.png)