当前位置:   article > 正文

【干货】被裁员前,我为公司做的15个前端基建分享~

前端基建项目

点击上方 前端Q,关注公众号

回复加群,加入前端Q技术交流群

前言

大家好,我是winty,分享一篇好文~

半年时间撑过了三轮裁员,还是在第四轮的时候被裁了,差一周时间就入职满一年了。去年7月份换了一家新公司,刚进公司的时候感觉蒸蒸日上,特别有朝气,氛围也很轻松。这一年除了负责业务开发外,还做了很多前端基建方面的工作,多次技术分享,开发了很多个ai应用,对机器学习ai这块产生了很多的兴趣。

工作期间也带了三个前端实习生,亦师亦友,结下了深厚的友谊。一个大专带了本科985研究生学历的实习生。不过不得不感叹学历太重要了,本科的实习生实习结束后工作难找,选择去考公。985研究生的大厂随便面试,一个去了美团,一个去了华为,但技术能力是没有太多的差距的。

在里面也认识了很多关系好的朋友,最开始一切都很轻松,可是从去年年底开始就传出了要裁员的消息,把我关系最好的一个前端同事给裁了,今年来了后又陆陆续续裁了两波,部门内只剩三个前端了。

部门也换了新领导,上周一上午刚开会制定了新的开发流程,以为会稳定下来,短时间内不会再裁了,可是下午正在敲代码的时候新部门领导还是把我喊走了,说需求不多了,让我交接一下工作,补偿是n+1,有了前几轮裁员的经历,我也知道迟早会有这么一天,在公司虽然做了很多事情,但架不住在职时间短,被裁成本低,和新领导又不熟。

晚上回来改了改简历,后面几天投了一下,现在成都的环境很严峻,对大专更是不友好,虽然考了专升本,但感觉作用也不是特别大,现在好多外包都要求全日制本科。去年七月份找工作还能一天约两个面试,今年七月份找一周都很难约到两个。

不过还是要多调整简历好好复习,努力去找工作,工作这五年再加上自己平时不断学习,在技术广度深度还有前端架构能力都有着很丰富的经验,还有强大的学习能力和解决问题的能力,大环境改变不了,只能改变自己,多学习,确保每一个面试都能表现的很好。后面也会分享一下面试准备和面试遇到的一些问题。

在整理资料的时候发现了去年入职新公司一个月后结合自己以前所学和公司前端现状,总结一些可以更加规范和优化的点,在组内进行一次技术分享,现在再看也感慨颇多,当时的这些优化点,这一年时间现在也基本上都完成了,又整理了一下,来记录一下。

一. 项目目录规范

文件目录组织现在常用的有两种方式,后面公司采用的第二种,更方便一些。两种方式没有最好的,只有更适合自己公司的,只要公司内部达成一致了,用哪一种都会很方便。

1.1 按功能类型来划分

按文件的功能类型来分,比如api组件页面路由hooksstore,不管是全局使用到的,还是单独页面局部使用到的,都按照功能类型放在src下面对应的目录里面统一管理。

  1. yaml
  2. 复制代码
  3. ├─src               #  项目目录
  4. │  ├─api                #  数据请求
  5. │  │  └─Home            #  首页页面api
  6. │  │  └─Kind            #  分类页面api
  7. │  ├─assets             #  资源
  8. │  │  ├─css             #  css资源
  9. │  │  └─images          #  图片资源
  10. │  ├─config             #  配置
  11. │  ├─components         #  组件
  12. │  │  ├─common            #  公共组件
  13. │  │  └─Home              #  首页页面组件
  14. │  │  └─Kind              #  分类页面组件
  15. │  ├─layout             #  布局
  16. │  ├─hooks              #  自定义hooks组件
  17. │  ├─routes             #  路由
  18. │  ├─store              #  状态管理
  19. │  │  └─Home              #  首页页面公共的状态
  20. │  │  └─Kind              #  分类页面公共的状态
  21. │  ├─pages              #  页面
  22. │  │  └─Home              #  首页页面
  23. │  │  └─Kind              #  分类页面
  24. │  ├─utils              #  工具
  25. │  └─main.ts            #  入口文件

1.2 按领域模型划分

按照页面功能划分,全局会用到的组件api等还是放到src下面全局管理,页面内部单独使用的api组件放到对应页面的文件夹里面,使用的时候不用上下查找文件,在当前页面文件夹下就能找到,比较方便,功能也内聚一些。

  1. yaml
  2. 复制代码
  3. ├─src               #  项目目录
  4. │  ├─assets             #  资源
  5. │  │  ├─css             #  css资源
  6. │  │  └─images          #  图片资源
  7. │  ├─config             #  配置
  8. │  ├─components         #  公共组件
  9. │  ├─layout             #  布局
  10. │  ├─hooks              #  自定义hooks组件
  11. │  ├─routes             #  路由
  12. │  ├─store              #  全局状态管理
  13. │  ├─pages              #  页面
  14. │  │  └─Home              #  首页页面
  15. │  │    └─components      #  Home页面组件文件夹
  16. │  │    ├─api             #  Home页面api文件夹
  17. │  │    ├─store           #  Home页面状态
  18. │  │    ├─index.tsx       #  Home页面
  19. │  │  └─Kind              #  分类页面
  20. │  ├─utils              #  工具
  21. │  └─main.ts            #  入口文件

二. 代码书写规范

规范比较多,这里只简单列举一下基本的规范约束和使用工具来自动化规范代码。

2.1 组件结构

react组件

  1. tsx
  2. 复制代码
  3. import React, { memo, useMemo } from 'react'
  4. interface ITitleProps {
  5.   title: string
  6. }
  7. const Title: React.FC<ITitleProps> = props => {
  8.   const { title } = props
  9.   return (
  10.     <h2>{title}</h2>
  11.   )
  12. }
  13. export default memo(Title)

ITitleProps 以I为开头代表类型,中间为语义化Title,后面Props为类型,代表是组件参数。

2.2 定义接口

例1: 登录接口,定义好参数类型和响应数据类型,参数类型直接定义params的类型,响应数据放在范型里面,需要在封装的时候就处理好这个范型。

  1. tsx
  2. 复制代码
  3. import { request } from '@/utils/request'
  4. /** 公共的接口响应范型 */
  5. export interface HttpSuccessResponse<T> {
  6.   code: number
  7.   message: string
  8.   data: T
  9. }
  10. /** 登录接口参数 */
  11. export interface ILoginParams {
  12.   username: string
  13.   password: string
  14. }
  15. /** 登录接口响应 */
  16. export interface ILoginData {
  17.   token: string
  18. }
  19. /* 用户登录接口 */
  20. export const loginApi = (params: ILoginApi) => {
  21.   return request.post<ILoginData>('/xxx', params)
  22. }

2.3 事件

on开头代表事件,这个只是规范,on要比handle短一点,哈哈。

  1. tsx
  2. 复制代码
  3. const onChange = () => {
  4. }

2.4 工具约束代码规范

除了约定俗称的规范,我们也需要借助一些工具和插件来协助我们更好的完成规范这件事情。

代码规范

  1. vscode[1]:统一前端编辑器。

  2. editorconfig[2]: 统一团队vscode编辑器默认配置。

  3. prettier[3]: 保存文件自动格式化代码。

  4. eslint[4]: 检测代码语法规范和错误。

  5. stylelint[5]: 检测和格式化样式文件语法

可以看我这篇文章:【前端工程化】配置React+ts企业级代码规范及样式格式和git提交规范[6]

git提交规范

  1. husky[7]:可以监听githooks[8]执行,在对应hook执行阶段做一些处理的操作。

  2. lint-staged[9]: 只检测暂存区文件代码,优化eslint检测速度。

  3. pre-commit[10]:githooks之一, 在commit提交前使用tsceslint对语法进行检测。

  4. commit-msg[11]:githooks之一,在commit提交前对commit备注信息进行检测。

  5. commitlint[12]:在githookspre-commit阶段对commit备注信息进行检测。

  6. commitizen[13]:git的规范化提交工具,辅助填写commit信息。

可以看我这篇文章:【前端工程化】配置React+ts企业级代码规范及样式格式和git提交规范[14]

三. 状态管理器优化和统一

3.1 优化状态管理

reactcontext封装了一个简单的状态管理器,有完整的类型提升,支持在组件内和外部使用,也发布到npm[15]了

  1. tsx
  2. 复制代码
  3. import React, { createContext,  useContext, ComponentType, ComponentProps } from 'react'
  4. /** 创建context组合useState状态Store */
  5. function createStore<T>(store: () => T) {
  6.   // eslint-disable-next-line
  7.   const ModelContext: any = {};
  8.   /** 使用model */
  9.   function useModel<K extends keyof T>(key: K) {
  10.     return useContext(ModelContext[key]) as T[K];
  11.   }
  12.   /** 当前的状态 */
  13.   let currentStore: T;
  14.   /** 上一次的状态 */
  15.   let prevStore: T;
  16.   /** 创建状态注入组件 */
  17.   function StoreProvider(props: { children: React.ReactNode }) {
  18.     currentStore = store();
  19.     /** 如果有上次的context状态,做一下浅对比,
  20.      * 如果状态没变,就复用上一次context的value指针,避免context重新渲染
  21.      */
  22.     if (prevStore) {
  23.       for (const key in prevStore) {
  24.         // @ts-ignore
  25.         if (shallow(prevStore[key], currentStore[key])) {
  26.           // @ts-ignore
  27.           currentStore[key] = prevStore[key];
  28.         }
  29.       }
  30.     }
  31.     prevStore = currentStore;
  32.     // @ts-ignore
  33.     let keys: any[] = Object.keys(currentStore);
  34.     let i = 0;
  35.     const length = keys.length;
  36.     /** 遍历状态,递归形成多层级嵌套Context */
  37.     function getContext<T, K extends keyof T>(
  38.       key: K,
  39.       val: T,
  40.       children: React.ReactNode,
  41.     ): JSX.Element {
  42.       const Context =
  43.         ModelContext[key] || (ModelContext[key] = createContext(val[key]));
  44.       const currentIndex = ++i;
  45.       /** 返回嵌套的Context */
  46.       return React.createElement(
  47.         Context.Provider,
  48.         {
  49.           value: val[key],
  50.         },
  51.         currentIndex < length
  52.           ? getContext(keys[currentIndex], val, children)
  53.           : children,
  54.       );
  55.     }
  56.     return getContext(keys[i], currentStore, props.children);
  57.   }
  58.   /** 获取当前状态, 方便在组件外部使用,也不会引起页面更新 */
  59.   function getModel<K extends keyof T>(key: K): T[K] {
  60.     return currentStore[key];
  61.   }
  62.   /** 连接Model注入到组件中 */
  63.   function connectModel<Selected, K extends keyof T>(
  64.     key: K,
  65.     selector: (state: T[K]) => Selected,
  66.   ) {
  67.     // eslint-disable-next-line func-names
  68.     // @ts-ignore
  69.     return function <P, C extends ComponentType<any>>(
  70.       WarpComponent: C,
  71.     ): ComponentType<Omit<ComponentProps<C>, keyof Selected>> {
  72.       const Connect = (props: P) => {
  73.         const val = useModel(key);
  74.         const state = selector(val);
  75.         // @ts-ignore
  76.         return React.createElement(WarpComponent, {
  77.           ...props,
  78.           ...state,
  79.         });
  80.       };
  81.       return Connect as unknown as ComponentType<
  82.         Omit<ComponentProps<C>, keyof Selected>
  83.       >;
  84.     };
  85.   }
  86.   return {
  87.     useModel,
  88.     connectModel,
  89.     StoreProvider,
  90.     getModel,
  91.   };
  92. }
  93. export default createStore
  94. /** 浅对比对象 */
  95. function Shallow<T>(obj1: T, obj2: T) {
  96.   if(obj1 === obj2) return true
  97.   if(Object.keys(obj1).length !== Object.keys(obj2).length) return false
  98.   for(let key in obj1) {
  99.     if(obj1[key] !== obj2[key]) return false
  100.   }
  101.   return true
  102. }

3.2 store目录结构

  1. yaml
  2. 复制代码
  3. ├─src               #  项目目录
  4. │  ├─store              #  全局状态管理
  5. │  │  └─modules           #  状态modules
  6. │  │    └─user.ts           #  用户信息状态
  7. │  │    ├─other.ts          #  其他全局状态
  8. │  │  ├─createStore.ts          #  封装的状态管理器
  9. │  │  └─index.ts          #  store入口页面

3.3 定义状态管理器

1. 在store/index.ts中引入

  1. tsx
  2. 复制代码
  3. import { useState } from 'react'
  4. /** 1. 引入createStore.ts */
  5. import createStore from './createStore'
  6. /** 2. 定义各个状态 */
  7. // user
  8. const userModel = () => {
  9.   const [ userInfo, setUserInfo ] = useState<{ name: string }>({ name: 'name' })
  10.   return { userInfo, setUserInfo }
  11. }
  12. // other
  13. const otherModel = () => {
  14.   const [ other, setOther ] = useState<number>(20)
  15.   return { other, setOther }
  16. }
  17. /** 3. 组合所有状态 */
  18. const store = createStore(() => ({
  19.   user: userModel(),
  20.   other: otherModel(),
  21. }))
  22. /** 向外暴露useModel, StoreProvider, getModel, connectModel */
  23. export const { useModel, StoreProvider, getModel, connectModel } = store

2. 在顶层通过StoreProvider注入状态

  1. tsx
  2. 复制代码
  3. // src/main.ts
  4. import React from 'react'
  5. import ReactDOM from 'react-dom'
  6. import App from '@/App'
  7. // 1. 引入StoreProvider
  8. import { StoreProvider } from '@/store'
  9. // 2. 使用StoreProvider包裹App组件
  10. ReactDOM.render(
  11.   <StoreProvider>
  12.     <App />
  13.   </StoreProvider>,
  14.   document.getElementById('root')
  15. )

3.4 使用状态管理器

1. 在函数组件中使用,借助useModel

  1. tsx
  2. 复制代码
  3. import React from 'react'
  4. import { useModel } from '@/store'
  5. function FunctionDemo() {
  6.   /** 通过useModel取出user状态 */
  7.   const { userInfo, setUserInfo } = useModel('user')
  8.   /** 在点击事件中调用setUserInfo改变状态 */
  9.   const onChangeUser = () => {
  10.     setUserInfo({
  11.       name: userInfo.name + '1',
  12.     })
  13.   }
  14.   // 展示userInfo.name
  15.   return (
  16.     <button onClick={onChangeUser}>{userInfo.name}--改变user中的状态</button>
  17.   )
  18. }
  19. export default FunctionDemo

2. 在class组件中使用,借助connectModel

  1. tsx
  2. 复制代码
  3. import React, { Component } from 'react'
  4. import { connectModel } from '@/store'
  5. // 定义class组件props
  6. interface IClassDemoProps {
  7.   setOther: React.Dispatch<React.SetStateAction<string>>
  8.   other: number
  9. }
  10. class ClassDemo extends Component<IClassDemoProps> {
  11.   // 通过this.props获取到方法修改状态
  12.   onChange = () => {
  13.     this.props.setOther(this.props.other + 1)
  14.   }
  15.   render() {
  16.     // 通过this.props获取到状态进行展示
  17.     return <button onClick={this.onChange}>{this.props.other}</button>
  18.   }
  19. }
  20. // 通过高阶组件connectModel把other状态中的属性和方法注入到类组件中
  21. export default connectModel('other',state => ({
  22.   other: state.other,
  23.   setOther: state.setOther
  24. }))(ClassDemo)

3. 在组件外使用, 借助getModel

也可以在组件内读取修改状态方法,不回引起更新

  1. tsx
  2. 复制代码
  3. import { getModel } from '@/store'
  4. export const onChangeUser = () => {
  5.   // 通过getModel读取usel状态,进行操作
  6.   const user = getModel('user')
  7.   user.setUserInfo({
  8.     name: user.userInfo.name + '1'
  9.   })
  10. }
  11. // 1秒后执行onChangeUser方法
  12. setTimeout(onChangeUser, 1000)

四. 本地存储统一管理

可以对localStoragesessionStorage还有cookie简单封装一下,封装后使用的好处:

  1. 自动序列化,存储的时候转字符串,取得时候再转回来。

  2. 类型自动推断,在实例化的时候传入类型,在设置和获取值的时候都会自动类型推断。

  3. 可以统一管理,把本地存储都放在一个文件里面,避免后期本地存储混乱不好维护问题。

  4. 抹平平台差异,这个思路web,小程序,移动端,桌面端都适合。

  1. tsx
  2. 复制代码
  3. // src/utils/storage.ts
  4. const prefix = 'xxx.'
  5. interface IStorage<T> {
  6.   key: string
  7.   defaultValue: T
  8. }
  9. export class LocalStorage<T> implements IStorage<T> {
  10.   key: string
  11.   defaultValue: T
  12.   constructor(key, defaultValue) {
  13.     this.key = prefix + key
  14.     this.defaultValue = defaultValue
  15.   }
  16.   /** 设置值 */
  17.   setItem(value: T) {
  18.     localStorage.setItem(this.key, JSON.stringify(value))
  19.   }
  20.   /** 获取值 */
  21.   getItem(): T {
  22.     const value = localStorage[this.key] && localStorage.getItem(this.key)
  23.     if (value === undefined) return this.defaultValue
  24.     try {
  25.       return value && value !== 'null' && value !== 'undefined'
  26.         ? (JSON.parse(value) as T)
  27.         : this.defaultValue
  28.     } catch (error) {
  29.       return value && value !== 'null' && value !== 'undefined'
  30.         ? (value as unknown as T)
  31.         : this.defaultValue
  32.     }
  33.   }
  34.   /** 删除值 */
  35.   removeItem() {
  36.     localStorage.removeItem(this.key)
  37.   }
  38. }

实例化封装的本地存储

  1. tsx
  2. 复制代码
  3. // src/common/storage.ts
  4. import { LocalStorage } from '@/utils/storage'
  5. /** 管理token */
  6. export const tokenStorage = new LocalStorage<string>('token''')
  7. /** 用户信息类型 */
  8. export interface IUser {
  9.     name?: string
  10.     age?: num
  11. }
  12. /** 管理用户信息 */
  13. export const userStorage = new Storage<IUser>('user', {})

页面内使用

  1. tsx
  2. 复制代码
  3. import React, { memo, useMemo } from 'react'
  4. import { userStorage } from '@/common/storage'
  5. interface ITitleProps {
  6.   title: string
  7. }
  8. const Title: React.FC<ITitleProps> = props => {
  9.   const { title } = props
  10.     
  11.   useEffect(() => {
  12.     userStorage.setItem({ name: '姓名', age: 18 })
  13.     const user = userStorage.getItem()
  14.     console.log(user) // { name: '姓名', age: 18 }
  15.   }, [])
  16.   return (
  17.     <h2>{title}</h2>
  18.   )
  19. }
  20. export default memo(Title)

五. 封装请求统一和项目解耦

5.1 现有的封装

项目现用的请求封装和项目业务逻辑耦合在一块,不方便直接复用,使用上比较麻烦,每次需要传GETPOST类型,GET参数要每次单独做处理,参数类型限制弱。

5.2 推荐使用

推荐直接使用fetch封装或axios,项目中基于次做二次封装,只关注和项目有关的逻辑,不关注请求的实现逻辑。在请求异常的时候不返回Promise.reject() ,而是返回一个对象,只是code改为异常状态的code,这样在页面中使用时,不用用try/catch包裹,只用if判断code是否正确就可以。

  1. tsx
  2. 复制代码
  3. import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'
  4. import { tokenStorage } from '@/common/storage'
  5. /** 封装axios的实例,方便多个url时的封装 */
  6. export const createAxiosIntance = (baseURL: string): AxiosInstance => {
  7.   const request = axios.create({ baseURL })
  8.   // 请求拦截器器
  9.   request.interceptors.request.use((config: AxiosRequestConfig) => {
  10.     config.headers['Authorization'] = tokenStorage.getItem()
  11.     return config
  12.   })
  13.   // 响应拦截器
  14.   request.interceptors.response.use(
  15.     response => {
  16.       const code = response.data.code
  17.       switch (code) {
  18.         case 0:
  19.           return response.data
  20.         case 401:
  21.           // 登录失效逻辑
  22.           return response.data || {}
  23.         default:
  24.           return response.data || {}
  25.       }
  26.     },
  27.     error => {
  28.       // 接口请求报错时,也返回对象,这样使用async/await就不需要加try/catch
  29.       // code为0为请求正常,不为0为请求异常,使用message提示
  30.       return { message: onErrorReason(error.message) }
  31.     }
  32.   )
  33.   return request
  34. }
  35. /** 解析http层面请求异常原因 */
  36. function onErrorReason(message: string): string {
  37.   if (message.includes('Network Error')) {
  38.     return '网络异常,请检查网络情况!'
  39.   }
  40.   if (message.includes('timeout')) {
  41.     return '请求超时,请重试!'
  42.   }
  43.   return '服务异常,请重试!'
  44. }
  45. export const request = createAxiosIntance('https://xxx')

5.3 使用

使用上面代码命名定义接口类型的loginApi例子

  1. tsx
  2. 复制代码
  3. /** 登录 */
  4. const onLogin = async () => {
  5.   const res = await loginApi(params)
  6.   if(res.code === 0) {
  7.     // 处理登录正常逻辑
  8.   } else {
  9.     message.error(res.message) // 错误提示也可以在封装时统一添加
  10.   }
  11. }

六. api接口管理统一

文件夹路径

  1. yaml
  2. 复制代码
  3. ├─pages                 #  页面
  4. │  ├─Login              #  登录页面
  5. │  │  └─api             #  api文件夹
  6. │  │    └─index.ts      #  api函数封装
  7. │  │    ├─types.ts      #  api的参数和响应类型

定义类型

  1. tsx
  2. 复制代码
  3. // api/types.ts
  4. /** 登录接口参数 */
  5. export interface ILoginParams {
  6.   username: string
  7.   password: string
  8. }
  9. /** 登录接口响应 */
  10. export interface ILoginData {
  11.   token: string
  12. }

定义请求接口

  1. tsx
  2. 复制代码
  3. import { request } from '@/utils/request'
  4. import { ILoginParams, ILoginData } from './types'
  5. /* 用户登录接口 */
  6. export const loginApi = (params: ILoginParams) => {
  7.   return request.post<ILoginData>('/distribute/school/login', params)
  8. }

使用请求接口

使用上面代码命名定义接口类型的loginApi例子

  1. tsx
  2. 复制代码
  3. /** 登录 */
  4. const onLogin = async () => {
  5.   const res = await loginApi(params)
  6.   if(res.code === 0) {
  7.     // 处理登录正常逻辑
  8.   } else {
  9.     message.error(res.message) // 错误提示也可以在封装时统一添加
  10.   }
  11. }

七. 函数库-通用方法抽离复用

把公司项目中常用的方法hooks抽离出来组成函数库,方便在各个项目中使用,通过编写函数方法,写jest单元测试,也可以提升组内成员的整体水平。当时组内前端不管是实习生还是正式成员都在参与函数库的建设,很多就有了 30+ 的函数和hooks,还在不断的增加。

是用了dumi2来开发的函数库,可以看我的这篇文章【前端工程化】使用dumi2搭建React组件库和函数库详细教程[16]

八. 组件库-通用组件抽离复用

公司项目多了会有很多公共的组件,可以抽离出来,方便其他项目复用,一般可以分为以下几种组件:

  1. UI组件

  2. 业务组件

  3. 功能组件:上拉刷新,滚动到底部加载更多,虚拟滚动,拖拽排序,图片懒加载..

由于公司技术栈主要是react,组件库也是采用了dumi2的方案,可以看我的这篇文章【前端工程化】使用dumi2搭建React组件库和函数库详细教程[17]

九. css超集和css模块化方案统一

css超集

使用less或者scss,看项目具体情况,能全项目统一就统一。

css模块化

vue使用自带的style scopedreact使用css-module方案。

开启也简单,以vite为例,默认支持,可以修改vite.config.ts配置:

  1. tsx
  2. 复制代码
  3. // vite.config.ts
  4. export default defineConfig({
  5.   css: {
  6.     // 配置 css-module
  7.     modules: {
  8.       // 开启 camelCase 格式变量名转换
  9.       localsConvention: 'camelCase',
  10.       // 类名格式,[local]是自己原本的类名,[hash:base64:5]是5位的hash值
  11.       generateScopedName: '[local]-[hash:base64:5]',
  12.     }
  13.   },
  14. })

使用的时候,样式文件命名后缀需要加上 .module,例如index.module.less

  1. less
  2. 复制代码
  3. // index.module.less
  4. .title {
  5.  font-size: 18px;
  6.   color: yellow;
  7. }

组件里面使用:

  1. tsx
  2. 复制代码
  3. import React, { memo, useMemo } from 'react'
  4. import styles from './index.module.less'
  5. interface ITitleProps {
  6.   title: string
  7. }
  8. const Title: React.FC<ITitleProps> = props => {
  9.   const { title } = props
  10.   return (
  11.     <h2 className={styles.title}>{title}</h2>
  12.   )
  13. }
  14. export default memo(Title)

编译后类名会变成title-[hash:5] ,可以有效避免样式冲突,减少起类名的痛苦。

十. 引入immer来优化性能和简化写法

Immer[18] 是 mobx 的作者写的一个 immutable 库,核心实现是利用 ES6 的 Proxy(不支持Proxy的环境会自动使用Object.defineProperty来实现),几乎以最小的成本实现了 js 的不可变数据结构,简单易用、体量小巧、设计巧妙,满足了我们对js不可变数据结构的需求。

1. 优化性能

修改用户信息

  1. tsx
  2. 复制代码
  3. const [ userInfo, setUserInfo ] = useState({ name: 'immer', info: { age: 6 } })
  4. const onChange = (age: number) => {
  5.   setUserInfo({...userInfo, info: {
  6.     ...userinfo.info,
  7.     age: age
  8.   }})
  9. }

上面某次修改age没有变,但setUserInfo时每次都生成了一个新对象,更新前后引用变化了,组件就会刷新。

使用immer后,age没变时不会生成新的引用,同时语法也更简洁,可以优化性能。

  1. tsx
  2. 复制代码
  3. import produce from 'immer'
  4. const [ userInfo, setUserInfo ] = useState({ name: 'immer', age: 5 })
  5. const onChange = (age: number) => {
  6.   setUserInfo(darft => {
  7.     darft.age = age
  8.   })
  9. }

2.简化写法

react遵循不可变数据流的理念,每次修改状态都要新生成一个引用,不能在原先的引用上进行修改,所以在对引用类型对象或者数组做操作时,总要浅拷贝一下,再来做处理,当修改的状态层级比较深的时候,写法会更复杂。

以数组为例,修改购物车某个商品的数量:

  1. tsx
  2. 复制代码
  3. import produce from 'immer'
  4. const [ list, setList ] = useState([{ price: 100, num: 1 }, { price: 200, num: 1 }])
  5. // 不使用用immer
  6. const onAdd = (index: number) => {
  7.   /** 不使用immer */
  8.   // const item = { ...list[index] }
  9.   // item.num++
  10.   // list[index] = item
  11.   // setList([...list])
  12.   /** 使用immer */
  13.   setList(
  14.     produce(darft => {
  15.       darft[index].num++
  16.     }),
  17.   )
  18. }

3. 可以用use-immer[19]简化写法:

  1. tsx
  2. 复制代码
  3. import useImmer from 'use-immer'
  4. const [ list, setList ] = useImmer([{ price: 100, num: 1 }, { price: 200, num: 1 }])
  5. const onAdd = (index: number) => {
  6.   setList(darft => {
  7.       darft[index].num++
  8.   })
  9. }

十一. 搭建npm私服

公司前端项目不推荐使用太多第三方包,可以自己搭建公司npm私服,来托管公司自己封装的状态管理库,请求库,组件库,以及脚手架clisdknpm包,方便复用和管理。

可以看我这两篇文章,都可以搭建npm私服:

【前端工程化】巧用阿里云oss服务打造前端npm私有仓库[20]

【前端工程化】使用verdaccio搭建公司npm私有库完整流程和踩坑记录[21]

十二. 各类型项目通用模版封装

可以提前根据公司的业务需求,封装出各个端对应通用开发模版,封装好项目目录结构,接口请求,状态管理,代码规范,git规范钩子,页面适配,权限,本地存储管理等等,来减少开发新项目时前期准备工作时间,也能更好的统一公司整体的代码规范。

  1. 通用后台管理系统基础模版封装

  2. 通用小程序基础模版封装

  3. 通用h5端基础模版封装

  4. 通用node端基础模版封装

  5. 其他类型的项目默认模版封装,减少重复工作。

十三. 搭建cli脚手架下载模版。

搭建类似vue-clivitecreate-react-app类的cli命令行脚手架来快速选择和下载封装好的模版,比git拉代码要方便。

具体cli脚手架的实现可以看我这篇文章:【前端工程化】从入门到精通,100行代码构建你的前端CLI脚手架之路[22]

十四. git操作规范

git操作规范也很重要,流程不规范很容易出现比较复杂的问题,要根据公司现有情况和业界比较好的实践方案制定一套适合自己公司的git flow开发规范,用各种限制方案来避免出现问题,这个具体流规范后面会总结一篇文章出来。

十五. 规范和使用文档输出文档站点

代码规范和git提交规范以及各个封装的库使用说明要输出成文档部署到线上,方便新同事快速熟悉和使用。

这个是很重要的,做了再多的基建和规范,如果没有一个公共的文文档来查阅,就没办法快速熟悉,所以要一个线上的规范文档,把所有的规范都写进去,可以用语雀。

作者:Ausra无忧
链接:https://juejin.cn/post/7256393626682163237
来源:稀土掘金

8a561b6b04303b5faaa8526ee4d789e0.png

往期推荐

线上BUG引起思考:package.json 中的 ^~ 该保留吗?

4084134ab76ed7beccf7ac15e25aabad.png

vite打包性能优化以及填坑

b9c9e1480957786a2f09d6ef746c01a9.png

字节跳动的前端工程化实践

6c1fbfbd69bf0a9aab58b22bcdd276ca.png


最后

  • 欢迎加我微信,拉你进技术群,长期交流学习...

  • 欢迎关注「前端Q」,认真学前端,做个专业的技术人...

9de461b6a75c0322ae7446ac57ae7f88.jpeg

a3692b1d53a7e7a964b4b1dc2f172b84.png

点个在看支持我吧

f08db7f7de95801fe9ba307005e0d7ba.gif

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/685400
推荐阅读
相关标签
  

闽ICP备14008679号