您现在的位置是:首页 >其他 >20230606----重返学习-react项目网站首页其他

20230606----重返学习-react项目

方朝端 2024-08-30 12:01:03
简介20230606----重返学习-react项目

day-086-eighty-six-20230606-react项目

react项目

构建React纯正项目

  • 从零开始构建React项目
    • 本项目不采用任何系解决方案(例如:淘系),就是基于最纯正的React实现开发

实际步骤

  1. 基于create-react-app创建工程化项目。

    • 第一步:根据react脚手架创建好通用react项目结构。

      npm i create-react-app -g
      create-react-app 项目名
      
    • 第二步:暴露配置。

      yarn eject 暴露webpack配置项
      
    • 第三步:修改脚手架默认的配置

      • 配置less:less/less-loader@8
        1. $ yarn add less less-loader@8
      • 配置@别名
        • 配置@别名,让其代表src目录
        • 方便写代码的时候,@有提示
      • 在生产环境下,自动去掉 console 和 debugger 等调试输出
      • 修改开发服务器IP地址和端口号
      • 修改生产环境打包后根目录
      • 加快项目的打包速度
        • 取消sourceMap调试文件的生成
        • 取消ESLint
      • 配置浏览器兼容
        • 处理浏览器兼容
          1. 最核心的部分还是设置 browserslist 浏览器兼容列表
            • css3和ES6语法都是基于这个列表进行转换的
          2. 想处理ES6 API的兼容,需要依赖react-app-polyfill
          3. ant-design mobile的兼容
      • 组件库的按需加载
      • 配置跨域代理:http-proxy-middleware
        1. $ yarn add http-proxy-middleware
      • 配置REM响应式布局的处理:lib-flexiblepostcss-pxtorem
      • 配置打包优化
  2. 准备一些项目开发必备的材料

    • axios的统一封装:
      1. axios请求的二次封装:
        1. 创建一个和axios一样的实例。
          • 因为一个网页可能会有发送多个不同服务器的请求。不同服务器的根路径或延时或处理都不一样。
            • 这样可以对不同域名的后端发送请求,进行不一样的封装。
        2. 针对post请求,对请求主体的信息进行格式化处理。
        3. 请求拦截器:在发送请求之前做的事情。
        4. 响应拦截器:在服务器返回结果和我们自己在组件调用之间。
      2. 对所有的api请求进行统一管理。
        1. 创建一个对象,内部存的都是接口的请求地址。
        2. 对象里的属性要不是一个请求完成后返回一个Promise实例对象的函数,要不就是包含这类函数的模块名。
        3. 返回这个包含了所有请求接口的对象。
          • 所有的请求,都要通过返回的这个对象来访问及请求。
      • 步骤示例:
        • React阶段/知乎日报/zhihu/src/api/http.js:axios请求的二次封装
        • React阶段/知乎日报/zhihu/src/api/index.js:api请求的统一管理。
    • 放置静态资源:
      • src/assets:
        • reset.min.css 清除浏览器默认样式
        • images 静态资源图片
          • css可以直接用相对路径来访问。
          • js代码中,需要用模块引入,之后再使用。
        • utils.js 自己封装的常用方法库。
  3. 配置好rem响应式布局 && 样式处理。

    • lib-flexible 设置rem和px换算比例的。
      • 根据设备宽度的变化自动计算。
      • html.style.fontSize=设备的宽度/10+'px';
      • 375px的设计稿1rem=37.5px:初始换算比例;
    • postcss-pxtorem 可以把我们写的px单位,按照当时的换算比例自动转换为rem单位,不需要我们自己算了。
    • babel-plugin-styled-components-px2rem 处理基于styled编写的样式。
  4. 配置路由管理。

    1. 首先根据效果图,做路由的分析。

      `/` 首页
      `/deatil/:id` 新闻详情
      `/personal` 个人中心
      `/mystore` 我的收藏
      `/update` 修改个人信息
      `/login` 登录和注册
      `/404` 错误页
      `*` 重定向到404页面。
      
      • 移动端一般只设置一级路由。
    2. 把需要用到的组件先创建出来。

      • 结构和样式可以暂时先不写。
    3. 编写路由表和路由的统一配置处理。

      1. 编写路由表。
      2. 路由的统一渲染处理。
        1. 路由懒加载。
        2. 导航守卫。
        3. 再实现withRouter()函数效果-自定义hook抽离版。
        4. 在组件指定位置渲染
    • 实际步骤:
      1. 编写路由表。
      2. 路由的统一渲染处理。
      3. 自己编写withRouter(),可抽离自定义hook。
      4. 在组件指定位置渲染,如根视图组件。
    • 代码:
      • 代码示例:
        • 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;
          
  5. 配置redux架子。

    1. 构建redux文件结构。
    2. 统一管理派发的行为标识action-types
    3. 统一设置派发行为标识的函数actions
    4. 统一设置处理函数reducers
    5. 生成仓库容器并在仓库容器中使用中间件。
      • 增强处理函数reducer和开发体验。
    6. 在入口文件中使用全局公共状态容器。
    • 代码:
      • 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
          
  6. 其它的基础框架配置

    • UI组件库的国际化处理。
    • 全局通用样式。
    • 解决移动端单击的300ms延迟处理。
  7. 逐一开发项目,注意组件的抽离封装。

    • 组件太大,超过1000行或500行,大组件拆分成小组件。
    • 组件的逻辑在其它组件使用,js代码逻辑抽离,更抽象。
      1. 一层指的是使用import导入的东西。具体最底层引用的到第三方js插件,或UI框架的UI组件,或工具函数utils.js的一个方法。
        • 不过个人觉得,工具函数也应该算是一个层级。
          • 因为一个项目的工具函数,并不通用。
          • 文档还少,具体功能只能看源码,有些工具函数还跳来跳去的。
            • 或者问同事那个函数是用来干什么的。
              • 但一般会麻烦到别人。因为有些工具函数有经验的人一眼懂,没经验的人得去猜,中间多了层沟通成本。
        • UI组件库不算一个层级,因为UI组件库文档比较好,有开源的人一起看,网上资料多。
        • 第三方插件也是如此,不过小众插件,最好加个说明。
          • 加个说明,说大概想要达到的效果,和主要可以用来做什么。很方便别人接手。
      2. 这个个人感觉不要过度封装,比如一个业务用的东西引用了四五层,看代码维护起来更累。
      3. 一般抽离个两三层就好了。
  8. 开发完毕后。

    • 项目优化。
      • js代码写得更高效。
      • DOM层级更简单。
      • css更简单或更炫或更流畅,前提是不用处理太多。
    • 封装提取。
      • 组件代码行数太多,拆分成多个。
      • 组件的代码其它地方也要用到,抽离出来,不同组件都相互引用。一般是抽离成一个hook的样子。
    • 自己测试。
      • 正常操作,功能能实现,无明显后端数据交互错误。
      • 一般进行两遍。
    • 内部测试,交给公司的测试去测。
      • 一般是数据传参出错,这个比较严重。
      • 或者是人性化交互出错,这个不太严重,可以与产品交流或自己修改。
    • 部署上线。
      • 前端提交代码到生产环境,主要是运维那边要处理的,比如代码在生产环境打包出问题。

项目开发

  • 日期处理

    • 用自己的工具函数
    • 用UI框架库的。
      • 在package.json中查看。
      • 先看组件库的配置国际化中的日期处理。
      • 再看组件库的日期选择器用的是什么。
      • 在node_modules中查找幽灵依赖。
        • 一般是dayjs。
        • 或者是moment.js。
  • 规则校验:

    • 一般是多次被调用的组件才需要。
    • 或者是别人也要用到的。
  • 计算属性

    • 如果一些计算,依赖于某个属性并且运算较为复杂,使用useMemo();
      • 较为复杂的定义是:创建了一个中间对象,或者是计算要用到十几次的循环。
  • 路由跳转的话,一般使用自己写的withRouter(),更明显一点地说明内部有了路由跳转。

    • 不过用hook函数的话,更简洁一些,维护起来应该更简单。看个人爱好。

进阶参考

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。