day-086-eighty-six-20230606-react项目
react项目
构建React纯正项目
- 从零开始构建React项目
- 本项目不采用任何系解决方案(例如:淘系),就是基于最纯正的React实现开发
实际步骤
-
基于create-react-app创建工程化项目。
-
第一步:根据react脚手架创建好通用react项目结构。
npm i create-react-app -g create-react-app 项目名
-
第二步:暴露配置。
yarn eject 暴露webpack配置项
-
第三步:修改脚手架默认的配置
- 配置less:less/less-loader@8
$ yarn add less less-loader@8
- 配置@别名
- 配置@别名,让其代表src目录
- 方便写代码的时候,@有提示
- 在生产环境下,自动去掉 console 和 debugger 等调试输出
- 修改开发服务器IP地址和端口号
- 修改生产环境打包后根目录
- 加快项目的打包速度
- 取消sourceMap调试文件的生成
- 取消ESLint
- 配置浏览器兼容
- 处理浏览器兼容
- 最核心的部分还是设置 browserslist 浏览器兼容列表
- css3和ES6语法都是基于这个列表进行转换的
- 想处理
ES6 API
的兼容,需要依赖react-app-polyfill
ant-design mobile
的兼容
- 最核心的部分还是设置 browserslist 浏览器兼容列表
- 处理浏览器兼容
- 组件库的按需加载
- 配置跨域代理:http-proxy-middleware
$ yarn add http-proxy-middleware
- 配置REM响应式布局的处理:
lib-flexible
、postcss-pxtorem
- 配置打包优化
- …
- 配置less:less/less-loader@8
-
-
准备一些项目开发必备的材料
- axios的统一封装:
- axios请求的二次封装:
- 创建一个和axios一样的实例。
- 因为一个网页可能会有发送多个不同服务器的请求。不同服务器的根路径或延时或处理都不一样。
- 这样可以对不同域名的后端发送请求,进行不一样的封装。
- 因为一个网页可能会有发送多个不同服务器的请求。不同服务器的根路径或延时或处理都不一样。
- 针对post请求,对请求主体的信息进行格式化处理。
- 请求拦截器:在发送请求之前做的事情。
- 响应拦截器:在服务器返回结果和我们自己在组件调用之间。
- 创建一个和axios一样的实例。
- 对所有的api请求进行统一管理。
- 创建一个对象,内部存的都是接口的请求地址。
- 对象里的属性要不是一个请求完成后返回一个Promise实例对象的函数,要不就是包含这类函数的模块名。
- 返回这个包含了所有请求接口的对象。
- 所有的请求,都要通过返回的这个对象来访问及请求。
- 步骤示例:
- React阶段/知乎日报/zhihu/src/api/http.js:axios请求的二次封装
- React阶段/知乎日报/zhihu/src/api/index.js:api请求的统一管理。
- axios请求的二次封装:
- 放置静态资源:
- src/assets:
- reset.min.css 清除浏览器默认样式
- images 静态资源图片
- css可以直接用相对路径来访问。
- js代码中,需要用模块引入,之后再使用。
- utils.js 自己封装的常用方法库。
- …
- src/assets:
- axios的统一封装:
-
配置好rem响应式布局 && 样式处理。
- lib-flexible 设置rem和px换算比例的。
- 根据设备宽度的变化自动计算。
html.style.fontSize=设备的宽度/10+'px'
;375px的设计稿
中1rem=37.5px
:初始换算比例;
postcss-pxtorem
可以把我们写的px单位
,按照当时的换算比例
自动转换为rem单位
,不需要我们自己算了。babel-plugin-styled-components-px2rem
处理基于styled编写
的样式。
- lib-flexible 设置rem和px换算比例的。
-
配置路由管理。
-
首先根据效果图,做路由的分析。
`/` 首页 `/deatil/:id` 新闻详情 `/personal` 个人中心 `/mystore` 我的收藏 `/update` 修改个人信息 `/login` 登录和注册 `/404` 错误页 `*` 重定向到404页面。
- 移动端一般只设置一级路由。
-
把需要用到的组件先创建出来。
- 结构和样式可以暂时先不写。
-
编写路由表和路由的统一配置处理。
- 编写路由表。
- 路由的统一渲染处理。
- 路由懒加载。
- 导航守卫。
- 再实现withRouter()函数效果-自定义hook抽离版。
- 在组件指定位置渲染
- 实际步骤:
- 编写路由表。
- 路由的统一渲染处理。
- 自己编写withRouter(),可抽离自定义hook。
- 在组件指定位置渲染,如根视图组件。
- 代码:
- 代码示例:
-
React阶段/知乎日报/zhihu/src/router/withRouter.jsx
import useRouteInfo from "./useRouteInfo"; export default function withRouter(Component) { return function (props) { const options = useRouteInfo(); return <Component {...props} {...options} />; }; }
-
React阶段/知乎日报/zhihu/src/router/useRouteInfo.jsx
import { useNavigate, useLocation, useParams, useSearchParams, useMatch, } from "react-router-dom"; import routes from "./routes"; export default function useRouteInfo(item) { let navigate = useNavigate(), location = useLocation(), params = useParams(), query = useSearchParams()[0], match = useMatch(location.pathname), route = null; if (item) { route = item; } else { let [, name] = location.pathname.match(/^\/([^/?#]*)/) || []; if (!name) name = "home"; item = routes.find((item) => item.name === name); if (item) route = item; } const options = { navigate, location, params, query, match, route, }; return options; }
-
React阶段/知乎日报/zhihu/src/router/routes.js
import { lazy } from "react"; import Home from "@/views/Home"; const routes = [ { path: "/", name: "home", meta: { title: "首页" }, component: Home, }, { path: "/detail/:id", name: "detail", meta: { title: "详情页" }, component: lazy(() => import("@/views/Detail")), }, { path: "/personal", name: "personal", meta: { title: "个人中心" }, component: lazy(() => import("@/views/Personal")), }, { path: "/login", name: "login", meta: { title: "登录/注册" }, component: lazy(() => import("@/views/Login")), }, { path: "/mystore", name: "mystore", meta: { title: "我的收藏" }, component: lazy(() => import("@/views/MyStore")), }, { path: "/update", name: "update", meta: { title: "修改个人信息" }, component: lazy(() => import("@/views/Update")), }, { path: "/404", name: "404", meta: { title: "404页面" }, component: lazy(() => import("@/views/Page404")), }, { path: "*", redirect: "/404", }, ]; export default routes;
-
React阶段/知乎日报/zhihu/src/router/index.jsx
import { Suspense } from "react"; import { Routes, Route, Navigate } from "react-router-dom"; import useRouteInfo from "./useRouteInfo"; import Loading from "@/components/Loading"; import routes from "./routes"; /* 路由匹配渲染的“前置守卫”:渲染组件之前做的事情 */ const Element = function Element({ item }) { let { meta, component: Component } = item; // 修改页面的标题 let title = meta?.title; document.title = title ? `${title} - 知乎日报` : `知乎日报`; // 把路由的相关信息作为属性传递给组件 const options = useRouteInfo(item); return <Component {...options} />; }; /* 路由规则配置 */ const RouterView = function RouterView() { return ( <Suspense fallback={<Loading />}> <Routes> {routes.map((item, index) => { let { path, redirect } = item; if (redirect) { return ( <Route key={index} path={path} element={<Navigate to={redirect} />} /> ); } return ( <Route key={index} path={path} element={<Element item={item} />} /> ); })} </Routes> </Suspense> ); }; export default RouterView;
-
- 代码示例:
-
-
配置redux架子。
- 构建redux文件结构。
- 统一管理派发的行为标识
action-types
。 - 统一设置派发行为标识的函数
actions
。 - 统一设置处理函数
reducers
。 - 生成仓库容器并在仓库容器中使用中间件。
- 增强
处理函数reducer
和开发体验。
- 增强
- 在入口文件中使用全局公共状态容器。
- 代码:
- React阶段/知乎日报/zhihu/src/store
-
React阶段/知乎日报/zhihu/src/store/actions
-
React阶段/知乎日报/zhihu/src/store/actions/baseAction.js
import * as AT from '../action-types' const baseAction = { } export default baseAction
-
React阶段/知乎日报/zhihu/src/store/actions/index.js
import baseAction from "./baseAction" const action = { base: baseAction } export default action
-
-
React阶段/知乎日报/zhihu/src/store/reducers
-
React阶段/知乎日报/zhihu/src/store/reducers/baseReducer.js
import * as AT from '../action-types' let initial = { } export default function baseReducer(state = initial, action) { state = { ...state } let { type } = action switch (type) { default: } return state }
-
React阶段/知乎日报/zhihu/src/store/reducers/index.js
import { combineReducers } from 'redux' import baseReducer from './baseReducer' const reducer = combineReducers({ base: baseReducer }) export default reducer
-
-
React阶段/知乎日报/zhihu/src/store/action-types.js
/* 管理派发的行为标识 */
-
React阶段/知乎日报/zhihu/src/store/index.js
import { legacy_createStore, applyMiddleware } from 'redux' import reduxLogger from 'redux-logger' import reduxThunk from 'redux-thunk' import reduxPromise from 'redux-promise' import reducer from './reducers' // 使用中间件 const env = process.env.NODE_ENV const middleware = [] if (env !== 'production') middleware.push(reduxLogger) middleware.push(reduxPromise) middleware.push(reduxThunk) // 创建STORE const store = legacy_createStore( reducer, applyMiddleware(...middleware) ) export default store
-
- React阶段/知乎日报/zhihu/src/store
-
其它的基础框架配置
- UI组件库的国际化处理。
- 全局通用样式。
- 解决移动端单击的300ms延迟处理。
-
逐一开发项目,注意组件的抽离封装。
- 组件太大,超过1000行或500行,大组件拆分成小组件。
- 组件的逻辑在其它组件使用,js代码逻辑抽离,更抽象。
- 一层指的是使用import导入的东西。具体最底层引用的到第三方js插件,或UI框架的UI组件,或工具函数utils.js的一个方法。
- 不过个人觉得,工具函数也应该算是一个层级。
- 因为一个项目的工具函数,并不通用。
- 文档还少,具体功能只能看源码,有些工具函数还跳来跳去的。
- 或者问同事那个函数是用来干什么的。
- 但一般会麻烦到别人。因为有些工具函数有经验的人一眼懂,没经验的人得去猜,中间多了层沟通成本。
- 或者问同事那个函数是用来干什么的。
- UI组件库不算一个层级,因为UI组件库文档比较好,有开源的人一起看,网上资料多。
- 第三方插件也是如此,不过小众插件,最好加个说明。
- 加个说明,说大概想要达到的效果,和主要可以用来做什么。很方便别人接手。
- 不过个人觉得,工具函数也应该算是一个层级。
- 这个个人感觉不要过度封装,比如一个业务用的东西引用了四五层,看代码维护起来更累。
- 一般抽离个两三层就好了。
- 一层指的是使用import导入的东西。具体最底层引用的到第三方js插件,或UI框架的UI组件,或工具函数utils.js的一个方法。
-
开发完毕后。
- 项目优化。
- js代码写得更高效。
- DOM层级更简单。
- css更简单或更炫或更流畅,前提是不用处理太多。
- 封装提取。
- 组件代码行数太多,拆分成多个。
- 组件的代码其它地方也要用到,抽离出来,不同组件都相互引用。一般是抽离成一个hook的样子。
- 自己测试。
- 正常操作,功能能实现,无明显后端数据交互错误。
- 一般进行两遍。
- 内部测试,交给公司的测试去测。
- 一般是数据传参出错,这个比较严重。
- 或者是人性化交互出错,这个不太严重,可以与产品交流或自己修改。
- 部署上线。
- 前端提交代码到生产环境,主要是运维那边要处理的,比如代码在生产环境打包出问题。
- 项目优化。
项目开发
-
日期处理
- 用自己的工具函数
- 用UI框架库的。
- 在package.json中查看。
- 先看组件库的配置国际化中的日期处理。
- 再看组件库的日期选择器用的是什么。
- 在node_modules中查找幽灵依赖。
- 一般是dayjs。
- 或者是moment.js。
-
规则校验:
- 一般是多次被调用的组件才需要。
- 或者是别人也要用到的。
-
计算属性
- 如果一些计算,依赖于某个属性并且运算较为复杂,使用
useMemo()
;- 较为复杂的定义是:创建了一个中间对象,或者是计算要用到十几次的循环。
- 如果一些计算,依赖于某个属性并且运算较为复杂,使用
-
路由跳转的话,一般使用自己写的withRouter(),更明显一点地说明内部有了路由跳转。
- 不过用hook函数的话,更简洁一些,维护起来应该更简单。看个人爱好。