<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【干貨】被裁員前,我為公司做的15個(gè)前端基建分享~

          共 33744字,需瀏覽 68分鐘

           ·

          2023-07-29 07:10

          大廠技術(shù)  高級(jí)前端  精選文章

          點(diǎn)擊上方 全站前端精選,關(guān)注公眾號(hào)

          回復(fù)1,加入高級(jí)前段交流

          前言

          半年時(shí)間撐過了三輪裁員,還是在第四輪的時(shí)候被裁了,差一周時(shí)間就入職滿一年了。去年7月份換了一家新公司,剛進(jìn)公司的時(shí)候感覺蒸蒸日上,特別有朝氣,氛圍也很輕松。這一年除了負(fù)責(zé)業(yè)務(wù)開發(fā)外,還做了很多前端基建方面的工作,多次技術(shù)分享,開發(fā)了很多個(gè)ai應(yīng)用,對(duì)機(jī)器學(xué)習(xí)ai這塊產(chǎn)生了很多的興趣。

          工作期間也帶了三個(gè)前端實(shí)習(xí)生,亦師亦友,結(jié)下了深厚的友誼。一個(gè)大專帶了本科985研究生學(xué)歷的實(shí)習(xí)生。不過不得不感嘆學(xué)歷太重要了,本科的實(shí)習(xí)生實(shí)習(xí)結(jié)束后工作難找,選擇去考公。985研究生的大廠隨便面試,一個(gè)去了美團(tuán),一個(gè)去了華為,但技術(shù)能力是沒有太多的差距的。

          在里面也認(rèn)識(shí)了很多關(guān)系好的朋友,最開始一切都很輕松,可是從去年年底開始就傳出了要裁員的消息,把我關(guān)系最好的一個(gè)前端同事給裁了,今年來了后又陸陸續(xù)續(xù)裁了兩波,部門內(nèi)只剩三個(gè)前端了。

          部門也換了新領(lǐng)導(dǎo),上周一上午剛開會(huì)制定了新的開發(fā)流程,以為會(huì)穩(wěn)定下來,短時(shí)間內(nèi)不會(huì)再裁了,可是下午正在敲代碼的時(shí)候新部門領(lǐng)導(dǎo)還是把我喊走了,說需求不多了,讓我交接一下工作,補(bǔ)償是n+1,有了前幾輪裁員的經(jīng)歷,我也知道遲早會(huì)有這么一天,在公司雖然做了很多事情,但架不住在職時(shí)間短,被裁成本低,和新領(lǐng)導(dǎo)又不熟。

          晚上回來改了改簡歷,后面幾天投了一下,現(xiàn)在成都的環(huán)境很嚴(yán)峻,對(duì)大專更是不友好,雖然考了專升本,但感覺作用也不是特別大,現(xiàn)在好多外包都要求全日制本科。去年七月份找工作還能一天約兩個(gè)面試,今年七月份找一周都很難約到兩個(gè)。

          不過還是要多調(diào)整簡歷好好復(fù)習(xí),努力去找工作,工作這五年再加上自己平時(shí)不斷學(xué)習(xí),在技術(shù)廣度深度還有前端架構(gòu)能力都有著很豐富的經(jīng)驗(yàn),還有強(qiáng)大的學(xué)習(xí)能力和解決問題的能力,大環(huán)境改變不了,只能改變自己,多學(xué)習(xí),確保每一個(gè)面試都能表現(xiàn)的很好。后面也會(huì)分享一下面試準(zhǔn)備和面試遇到的一些問題。

          在整理資料的時(shí)候發(fā)現(xiàn)了去年入職新公司一個(gè)月后結(jié)合自己以前所學(xué)和公司前端現(xiàn)狀,總結(jié)一些可以更加規(guī)范和優(yōu)化的點(diǎn),在組內(nèi)進(jìn)行一次技術(shù)分享,現(xiàn)在再看也感慨頗多,當(dāng)時(shí)的這些優(yōu)化點(diǎn),這一年時(shí)間現(xiàn)在也基本上都完成了,又整理了一下,來記錄一下。

          一. 項(xiàng)目目錄規(guī)范

          文件目錄組織現(xiàn)在常用的有兩種方式,后面公司采用的第二種,更方便一些。兩種方式?jīng)]有最好的,只有更適合自己公司的,只要公司內(nèi)部達(dá)成一致了,用哪一種都會(huì)很方便。

          1.1 按功能類型來劃分

          按文件的功能類型來分,比如api組件頁面路由hooksstore,不管是全局使用到的,還是單獨(dú)頁面局部使用到的,都按照功能類型放在src下面對(duì)應(yīng)的目錄里面統(tǒng)一管理。

          yaml
          復(fù)制代碼
          ├─src               #  項(xiàng)目目錄
          │  ├─api                #  數(shù)據(jù)請(qǐng)求
          │  │  └─Home            #  首頁頁面api
          │  │  └─Kind            #  分類頁面api
          │  ├─assets             #  資源
          │  │  ├─css             #  css資源
          │  │  └─images          #  圖片資源
          │  ├─config             #  配置
          │  ├─components         #  組件
          │  │  ├─common            #  公共組件
          │  │  └─Home              #  首頁頁面組件
          │  │  └─Kind              #  分類頁面組件
          │  ├─layout             #  布局
          │  ├─hooks              #  自定義hooks組件
          │  ├─routes             #  路由
          │  ├─store              #  狀態(tài)管理
          │  │  └─Home              #  首頁頁面公共的狀態(tài)
          │  │  └─Kind              #  分類頁面公共的狀態(tài)
          │  ├─pages              #  頁面
          │  │  └─Home              #  首頁頁面
          │  │  └─Kind              #  分類頁面
          │  ├─utils              #  工具
          │  └─main.ts            #  入口文件

          1.2 按領(lǐng)域模型劃分

          按照頁面功能劃分,全局會(huì)用到的組件api等還是放到src下面全局管理,頁面內(nèi)部單獨(dú)使用的api組件放到對(duì)應(yīng)頁面的文件夾里面,使用的時(shí)候不用上下查找文件,在當(dāng)前頁面文件夾下就能找到,比較方便,功能也內(nèi)聚一些。

          yaml
          復(fù)制代碼
          ├─src               #  項(xiàng)目目錄
          │  ├─assets             #  資源
          │  │  ├─css             #  css資源
          │  │  └─images          #  圖片資源
          │  ├─config             #  配置
          │  ├─components         #  公共組件
          │  ├─layout             #  布局
          │  ├─hooks              #  自定義hooks組件
          │  ├─routes             #  路由
          │  ├─store              #  全局狀態(tài)管理
          │  ├─pages              #  頁面
          │  │  └─Home              #  首頁頁面
          │  │    └─components      #  Home頁面組件文件夾
          │  │    ├─api             #  Home頁面api文件夾
          │  │    ├─store           #  Home頁面狀態(tài)
          │  │    ├─index.tsx       #  Home頁面
          │  │  └─Kind              #  分類頁面
          │  ├─utils              #  工具
          │  └─main.ts            #  入口文件

          二. 代碼書寫規(guī)范

          規(guī)范比較多,這里只簡單列舉一下基本的規(guī)范約束和使用工具來自動(dòng)化規(guī)范代碼。

          2.1 組件結(jié)構(gòu)

          react組件

          tsx
          復(fù)制代碼
          import React, { memo, useMemo } from 'react'

          interface ITitleProps {
            title: string
          }

          const Title: React.FC<ITitleProps> = props => {
            const { title } = props

            return (
              <h2>{title}</h2>
            )
          }

          export default memo(Title)

          ITitleProps 以I為開頭代表類型,中間為語義化Title,后面Props為類型,代表是組件參數(shù)。

          2.2 定義接口

          例1: 登錄接口,定義好參數(shù)類型和響應(yīng)數(shù)據(jù)類型,參數(shù)類型直接定義params的類型,響應(yīng)數(shù)據(jù)放在范型里面,需要在封裝的時(shí)候就處理好這個(gè)范型。

          tsx
          復(fù)制代碼
          import { request } from '@/utils/request'

          /** 公共的接口響應(yīng)范型 */
          export interface HttpSuccessResponse<T> {
            code: number
            message: string
            data: T
          }

          /** 登錄接口參數(shù) */
          export interface ILoginParams {
            username: string
            password: string
          }

          /** 登錄接口響應(yīng) */
          export interface ILoginData {
            token: string
          }

          /* 用戶登錄接口 */
          export const loginApi = (params: ILoginApi) => {
            return request.post<ILoginData>('/xxx', params)
          }

          2.3 事件

          on開頭代表事件,這個(gè)只是規(guī)范,on要比handle短一點(diǎn),哈哈。

          tsx
          復(fù)制代碼
          const onChange = () => {

          }

          2.4 工具約束代碼規(guī)范

          除了約定俗稱的規(guī)范,我們也需要借助一些工具和插件來協(xié)助我們更好的完成規(guī)范這件事情。

          代碼規(guī)范

          1. vscode[1]:統(tǒng)一前端編輯器。
          2. editorconfig[2]: 統(tǒng)一團(tuán)隊(duì)vscode編輯器默認(rèn)配置。
          3. prettier[3]: 保存文件自動(dòng)格式化代碼。
          4. eslint[4]: 檢測代碼語法規(guī)范和錯(cuò)誤。
          5. stylelint[5]: 檢測和格式化樣式文件語法

          可以看我這篇文章:【前端工程化】配置React+ts企業(yè)級(jí)代碼規(guī)范及樣式格式和git提交規(guī)范[6]

          git提交規(guī)范

          1. husky[7]:可以監(jiān)聽githooks[8]執(zhí)行,在對(duì)應(yīng)hook執(zhí)行階段做一些處理的操作。
          2. lint-staged[9]: 只檢測暫存區(qū)文件代碼,優(yōu)化eslint檢測速度。
          3. pre-commit[10]githooks之一, 在commit提交前使用tsceslint對(duì)語法進(jìn)行檢測。
          4. commit-msg[11]githooks之一,在commit提交前對(duì)commit備注信息進(jìn)行檢測。
          5. commitlint[12]:在githookspre-commit階段對(duì)commit備注信息進(jìn)行檢測。
          6. commitizen[13]git的規(guī)范化提交工具,輔助填寫commit信息。

          可以看我這篇文章:【前端工程化】配置React+ts企業(yè)級(jí)代碼規(guī)范及樣式格式和git提交規(guī)范[14]

          三. 狀態(tài)管理器優(yōu)化和統(tǒng)一

          3.1 優(yōu)化狀態(tài)管理

          reactcontext封裝了一個(gè)簡單的狀態(tài)管理器,有完整的類型提升,支持在組件內(nèi)和外部使用,也發(fā)布到npm[15]

          tsx
          復(fù)制代碼
          import React, { createContext,  useContext, ComponentType, ComponentProps } from 'react'

          /** 創(chuàng)建context組合useState狀態(tài)Store */
          function createStore<T>(store: () => T) {
            // eslint-disable-next-line
            const ModelContext: any = {};

            /** 使用model */
            function useModel<K extends keyof T>(key: K) {
              return useContext(ModelContext[key]) as T[K];
            }

            /** 當(dāng)前的狀態(tài) */
            let currentStore: T;
            /** 上一次的狀態(tài) */
            let prevStore: T;

            /** 創(chuàng)建狀態(tài)注入組件 */
            function StoreProvider(props: { children: React.ReactNode }) {
              currentStore = store();
              /** 如果有上次的context狀態(tài),做一下淺對(duì)比,
               * 如果狀態(tài)沒變,就復(fù)用上一次context的value指針,避免context重新渲染
               */
              if (prevStore) {
                for (const key in prevStore) {
                  // @ts-ignore
                  if (shallow(prevStore[key], currentStore[key])) {
                    // @ts-ignore
                    currentStore[key] = prevStore[key];
                  }
                }
              }
              prevStore = currentStore;
              // @ts-ignore
              let keys: any[] = Object.keys(currentStore);
              let i = 0;
              const length = keys.length;
              /** 遍歷狀態(tài),遞歸形成多層級(jí)嵌套Context */
              function getContext<T, K extends keyof T>(
                key: K,
                val: T,
                children: React.ReactNode,
              ): JSX.Element {
                const Context =
                  ModelContext[key] || (ModelContext[key] = createContext(val[key]));
                const currentIndex = ++i;
                /** 返回嵌套的Context */
                return React.createElement(
                  Context.Provider,
                  {
                    value: val[key],
                  },
                  currentIndex < length
                    ? getContext(keys[currentIndex], val, children)
                    : children,
                );
              }
              return getContext(keys[i], currentStore, props.children);
            }

            /** 獲取當(dāng)前狀態(tài), 方便在組件外部使用,也不會(huì)引起頁面更新 */
            function getModel<K extends keyof T>(key: K): T[K] {
              return currentStore[key];
            }

            /** 連接Model注入到組件中 */
            function connectModel<Selected, K extends keyof T>(
              key: K,
              selector: (state: T[K]) => Selected,
            ) {
              // eslint-disable-next-line func-names
              // @ts-ignore
              return function <P, C extends ComponentType<any>>(
                WarpComponent: C,
              ): ComponentType<Omit<ComponentProps<C>, keyof Selected>> {
                const Connect = (props: P) => {
                  const val = useModel(key);
                  const state = selector(val);
                  // @ts-ignore
                  return React.createElement(WarpComponent, {
                    ...props,
                    ...state,
                  });
                };
                return Connect as unknown as ComponentType<
                  Omit<ComponentProps<C>, keyof Selected>
                >;
              };
            }

            return {
              useModel,
              connectModel,
              StoreProvider,
              getModel,
            };
          }

          export default createStore

          /** 淺對(duì)比對(duì)象 */
          function Shallow<T>(obj1: T, obj2: T) {
            if(obj1 === obj2) return true
            if(Object.keys(obj1).length !== Object.keys(obj2).length) return false
            for(let key in obj1) {
              if(obj1[key] !== obj2[key]) return false
            }
            return true
          }

          3.2 store目錄結(jié)構(gòu)

          yaml
          復(fù)制代碼
          ├─src               #  項(xiàng)目目錄
          │  ├─store              #  全局狀態(tài)管理
          │  │  └─modules           #  狀態(tài)modules
          │  │    └─user.ts           #  用戶信息狀態(tài)
          │  │    ├─other.ts          #  其他全局狀態(tài)
          │  │  ├─createStore.ts          #  封裝的狀態(tài)管理器
          │  │  └─index.ts          #  store入口頁面

          3.3 定義狀態(tài)管理器

          1. 在store/index.ts中引入

          tsx
          復(fù)制代碼
          import { useState } from 'react'

          /** 1. 引入createStore.ts */
          import createStore from './createStore'

          /** 2. 定義各個(gè)狀態(tài) */
          // user
          const userModel = () => {
            const [ userInfo, setUserInfo ] = useState<{ name: string }>({ name: 'name' })
            return { userInfo, setUserInfo }
          }

          // other
          const otherModel = () => {
            const [ other, setOther ] = useState<number>(20)
            return { other, setOther }
          }

          /** 3. 組合所有狀態(tài) */
          const store = createStore(() => ({
            user: userModel(),
            other: otherModel(),
          }))

          /** 向外暴露useModel, StoreProvider, getModel, connectModel */
          export const { useModel, StoreProvider, getModel, connectModel } = store

          2. 在頂層通過StoreProvider注入狀態(tài)

          tsx
          復(fù)制代碼
          // src/main.ts
          import React from 'react'
          import ReactDOM from 'react-dom'
          import App from '@/App'
          // 1. 引入StoreProvider
          import { StoreProvider } from '@/store'

          // 2. 使用StoreProvider包裹App組件
          ReactDOM.render(
            <StoreProvider>
              <App />
            </StoreProvider>,
            document.getElementById('root')
          )

          3.4 使用狀態(tài)管理器

          1. 在函數(shù)組件中使用,借助useModel

          tsx
          復(fù)制代碼
          import React from 'react'
          import { useModel } from '@/store'

          function FunctionDemo() {

            /** 通過useModel取出user狀態(tài) */
            const { userInfo, setUserInfo } = useModel('user')

            /** 在點(diǎn)擊事件中調(diào)用setUserInfo改變狀態(tài) */
            const onChangeUser = () => {
              setUserInfo({
                name: userInfo.name + '1',
              })
            }

            // 展示userInfo.name
            return (
              <button onClick={onChangeUser}>{userInfo.name}--改變user中的狀態(tài)</button>
            )
          }

          export default FunctionDemo

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

          tsx
          復(fù)制代碼
          import React, { Component } from 'react'
          import { connectModel } from '@/store'

          // 定義class組件props
          interface IClassDemoProps {
            setOther: React.Dispatch<React.SetStateAction<string>>
            other: number
          }

          class ClassDemo extends Component<IClassDemoProps> {
            // 通過this.props獲取到方法修改狀態(tài)
            onChange = () => {
              this.props.setOther(this.props.other + 1)
            }
            render() {
              // 通過this.props獲取到狀態(tài)進(jìn)行展示
              return <button onClick={this.onChange}>{this.props.other}</button>
            }
          }

          // 通過高階組件connectModel把other狀態(tài)中的屬性和方法注入到類組件中
          export default connectModel('other',state => ({
            other: state.other,
            setOther: state.setOther
          }))(ClassDemo)

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

          也可以在組件內(nèi)讀取修改狀態(tài)方法,不回引起更新

          tsx
          復(fù)制代碼
          import { getModel } from '@/store'

          export const onChangeUser = () => {
            // 通過getModel讀取usel狀態(tài),進(jìn)行操作
            const user = getModel('user')
            user.setUserInfo({
              name: user.userInfo.name + '1'
            })
          }

          // 1秒后執(zhí)行onChangeUser方法
          setTimeout(onChangeUser, 1000)

          四. 本地存儲(chǔ)統(tǒng)一管理

          可以對(duì)localStoragesessionStorage還有cookie簡單封裝一下,封裝后使用的好處:

          1. 自動(dòng)序列化,存儲(chǔ)的時(shí)候轉(zhuǎn)字符串,取得時(shí)候再轉(zhuǎn)回來。
          2. 類型自動(dòng)推斷,在實(shí)例化的時(shí)候傳入類型,在設(shè)置和獲取值的時(shí)候都會(huì)自動(dòng)類型推斷。
          3. 可以統(tǒng)一管理,把本地存儲(chǔ)都放在一個(gè)文件里面,避免后期本地存儲(chǔ)混亂不好維護(hù)問題。
          4. 抹平平臺(tái)差異,這個(gè)思路web,小程序,移動(dòng)端,桌面端都適合。
          tsx
          復(fù)制代碼
          // src/utils/storage.ts
          const prefix = 'xxx.'

          interface IStorage<T> {
            key: string
            defaultValue: T
          }
          export class LocalStorage<T> implements IStorage<T> {
            key: string
            defaultValue: T
            constructor(key, defaultValue) {
              this.key = prefix + key
              this.defaultValue = defaultValue
            }
            /** 設(shè)置值 */
            setItem(value: T) {
              localStorage.setItem(this.key, JSON.stringify(value))
            }
            /** 獲取值 */
            getItem(): T {
              const value = localStorage[this.key] && localStorage.getItem(this.key)
              if (value === undefined) return this.defaultValue
              try {
                return value && value !== 'null' && value !== 'undefined'
                  ? (JSON.parse(value) as T)
                  : this.defaultValue
              } catch (error) {
                return value && value !== 'null' && value !== 'undefined'
                  ? (value as unknown as T)
                  : this.defaultValue
              }
            }
            /** 刪除值 */
            removeItem() {
              localStorage.removeItem(this.key)
            }
          }

          實(shí)例化封裝的本地存儲(chǔ)

          tsx
          復(fù)制代碼
          // src/common/storage.ts
          import { LocalStorage } from '@/utils/storage'

          /** 管理token */
          export const tokenStorage = new LocalStorage<string>('token''')

          /** 用戶信息類型 */
          export interface IUser {
              name?: string
              age?: num
          }

          /** 管理用戶信息 */
          export const userStorage = new Storage<IUser>('user', {})

          頁面內(nèi)使用

          tsx
          復(fù)制代碼
          import React, { memo, useMemo } from 'react'
          import { userStorage } from '@/common/storage'

          interface ITitleProps {
            title: string
          }

          const Title: React.FC<ITitleProps> = props => {
            const { title } = props
              
            useEffect(() => {
              userStorage.setItem({ name: '姓名', age: 18 })
              const user = userStorage.getItem()
              console.log(user) // { name: '姓名', age: 18 }
            }, [])

            return (
              <h2>{title}</h2>
            )
          }

          export default memo(Title)

          五. 封裝請(qǐng)求統(tǒng)一和項(xiàng)目解耦

          5.1 現(xiàn)有的封裝

          項(xiàng)目現(xiàn)用的請(qǐng)求封裝和項(xiàng)目業(yè)務(wù)邏輯耦合在一塊,不方便直接復(fù)用,使用上比較麻煩,每次需要傳GETPOST類型,GET參數(shù)要每次單獨(dú)做處理,參數(shù)類型限制弱。

          5.2 推薦使用

          推薦直接使用fetch封裝或axios,項(xiàng)目中基于次做二次封裝,只關(guān)注和項(xiàng)目有關(guān)的邏輯,不關(guān)注請(qǐng)求的實(shí)現(xiàn)邏輯。在請(qǐng)求異常的時(shí)候不返回Promise.reject() ,而是返回一個(gè)對(duì)象,只是code改為異常狀態(tài)的code,這樣在頁面中使用時(shí),不用用try/catch包裹,只用if判斷code是否正確就可以。

          tsx
          復(fù)制代碼
          import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'
          import { tokenStorage } from '@/common/storage'
          /** 封裝axios的實(shí)例,方便多個(gè)url時(shí)的封裝 */
          export const createAxiosIntance = (baseURL: string): AxiosInstance => {
            const request = axios.create({ baseURL })
            // 請(qǐng)求攔截器器
            request.interceptors.request.use((config: AxiosRequestConfig) => {
              config.headers['Authorization'] = tokenStorage.getItem()
              return config
            })
            // 響應(yīng)攔截器
            request.interceptors.response.use(
              response => {
                const code = response.data.code
                switch (code) {
                  case 0:
                    return response.data
                  case 401:
                    // 登錄失效邏輯
                    return response.data || {}
                  default:
                    return response.data || {}
                }
              },
              error => {
                // 接口請(qǐng)求報(bào)錯(cuò)時(shí),也返回對(duì)象,這樣使用async/await就不需要加try/catch
                // code為0為請(qǐng)求正常,不為0為請(qǐng)求異常,使用message提示
                return { message: onErrorReason(error.message) }
              }
            )
            return request
          }

          /** 解析http層面請(qǐng)求異常原因 */
          function onErrorReason(message: string): string {
            if (message.includes('Network Error')) {
              return '網(wǎng)絡(luò)異常,請(qǐng)檢查網(wǎng)絡(luò)情況!'
            }
            if (message.includes('timeout')) {
              return '請(qǐng)求超時(shí),請(qǐng)重試!'
            }
            return '服務(wù)異常,請(qǐng)重試!'
          }

          export const request = createAxiosIntance('https://xxx')

          5.3 使用

          使用上面代碼命名定義接口類型的loginApi例子

          tsx
          復(fù)制代碼
          /** 登錄 */
          const onLogin = async () => {
            const res = await loginApi(params)
            if(res.code === 0) {
              // 處理登錄正常邏輯
            } else {
              message.error(res.message) // 錯(cuò)誤提示也可以在封裝時(shí)統(tǒng)一添加
            }
          }

          六. api接口管理統(tǒng)一

          文件夾路徑

          yaml
          復(fù)制代碼
          ├─pages                 #  頁面
          │  ├─Login              #  登錄頁面
          │  │  └─api             #  api文件夾
          │  │    └─index.ts      #  api函數(shù)封裝
          │  │    ├─types.ts      #  api的參數(shù)和響應(yīng)類型

          定義類型

          tsx
          復(fù)制代碼
          // api/types.ts

          /** 登錄接口參數(shù) */
          export interface ILoginParams {
            username: string
            password: string
          }

          /** 登錄接口響應(yīng) */
          export interface ILoginData {
            token: string
          }

          定義請(qǐng)求接口

          tsx
          復(fù)制代碼
          import { request } from '@/utils/request'
          import { ILoginParams, ILoginData } from './types'

          /* 用戶登錄接口 */
          export const loginApi = (params: ILoginParams) => {
            return request.post<ILoginData>('/distribute/school/login', params)
          }

          使用請(qǐng)求接口

          使用上面代碼命名定義接口類型的loginApi例子

          tsx
          復(fù)制代碼
          /** 登錄 */
          const onLogin = async () => {
            const res = await loginApi(params)
            if(res.code === 0) {
              // 處理登錄正常邏輯
            } else {
              message.error(res.message) // 錯(cuò)誤提示也可以在封裝時(shí)統(tǒng)一添加
            }
          }

          七. 函數(shù)庫-通用方法抽離復(fù)用

          把公司項(xiàng)目中常用的方法hooks抽離出來組成函數(shù)庫,方便在各個(gè)項(xiàng)目中使用,通過編寫函數(shù)方法,寫jest單元測試,也可以提升組內(nèi)成員的整體水平。當(dāng)時(shí)組內(nèi)前端不管是實(shí)習(xí)生還是正式成員都在參與函數(shù)庫的建設(shè),很多就有了 30+ 的函數(shù)和hooks,還在不斷的增加。

          是用了dumi2來開發(fā)的函數(shù)庫,可以看我的這篇文章【前端工程化】使用dumi2搭建React組件庫和函數(shù)庫詳細(xì)教程[16]

          八. 組件庫-通用組件抽離復(fù)用

          公司項(xiàng)目多了會(huì)有很多公共的組件,可以抽離出來,方便其他項(xiàng)目復(fù)用,一般可以分為以下幾種組件:

          1. UI組件
          2. 業(yè)務(wù)組件
          3. 功能組件:上拉刷新,滾動(dòng)到底部加載更多,虛擬滾動(dòng),拖拽排序,圖片懶加載..

          由于公司技術(shù)棧主要是react,組件庫也是采用了dumi2的方案,可以看我的這篇文章【前端工程化】使用dumi2搭建React組件庫和函數(shù)庫詳細(xì)教程[17]

          九. css超集和css模塊化方案統(tǒng)一

          css超集

          使用less或者scss,看項(xiàng)目具體情況,能全項(xiàng)目統(tǒng)一就統(tǒng)一。

          css模塊化

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

          開啟也簡單,以vite為例,默認(rèn)支持,可以修改vite.config.ts配置:

          tsx
          復(fù)制代碼
          // vite.config.ts
          export default defineConfig({
            css: {
              // 配置 css-module
              modules: {
                // 開啟 camelCase 格式變量名轉(zhuǎn)換
                localsConvention: 'camelCase',
                // 類名格式,[local]是自己原本的類名,[hash:base64:5]是5位的hash
                generateScopedName: '[local]-[hash:base64:5]',
              }
            },
          })

          使用的時(shí)候,樣式文件命名后綴需要加上 .module,例如index.module.less

          less
          復(fù)制代碼
          // index.module.less
          .title {
           font-size: 18px;
            color: yellow;
          }

          組件里面使用:

          tsx
          復(fù)制代碼
          import React, { memo, useMemo } from 'react'
          import styles from './index.module.less'

          interface ITitleProps {
            title: string
          }

          const Title: React.FC<ITitleProps> = props => {
            const { title } = props

            return (
              <h2 className={styles.title}>{title}</h2>
            )
          }

          export default memo(Title)

          編譯后類名會(huì)變成title-[hash:5] ,可以有效避免樣式?jīng)_突,減少起類名的痛苦。

          十. 引入immer來優(yōu)化性能和簡化寫法

          Immer[18] 是 mobx 的作者寫的一個(gè) immutable 庫,核心實(shí)現(xiàn)是利用 ES6 的 Proxy(不支持Proxy的環(huán)境會(huì)自動(dòng)使用Object.defineProperty來實(shí)現(xiàn)),幾乎以最小的成本實(shí)現(xiàn)了 js 的不可變數(shù)據(jù)結(jié)構(gòu),簡單易用、體量小巧、設(shè)計(jì)巧妙,滿足了我們對(duì)js不可變數(shù)據(jù)結(jié)構(gòu)的需求。

          1. 優(yōu)化性能

          修改用戶信息

          tsx
          復(fù)制代碼
          const [ userInfo, setUserInfo ] = useState({ name: 'immer', info: { age: 6 } })
          const onChange = (age: number) => {
            setUserInfo({...userInfo, info: {
              ...userinfo.info,
              age: age
            }})
          }

          上面某次修改age沒有變,但setUserInfo時(shí)每次都生成了一個(gè)新對(duì)象,更新前后引用變化了,組件就會(huì)刷新。

          使用immer后,age沒變時(shí)不會(huì)生成新的引用,同時(shí)語法也更簡潔,可以優(yōu)化性能。

          tsx
          復(fù)制代碼
          import produce from 'immer'

          const [ userInfo, setUserInfo ] = useState({ name: 'immer', age: 5 })
          const onChange = (age: number) => {
            setUserInfo(darft => {
              darft.age = age
            })
          }

          2.簡化寫法

          react遵循不可變數(shù)據(jù)流的理念,每次修改狀態(tài)都要新生成一個(gè)引用,不能在原先的引用上進(jìn)行修改,所以在對(duì)引用類型對(duì)象或者數(shù)組做操作時(shí),總要淺拷貝一下,再來做處理,當(dāng)修改的狀態(tài)層級(jí)比較深的時(shí)候,寫法會(huì)更復(fù)雜。

          以數(shù)組為例,修改購物車某個(gè)商品的數(shù)量:

          tsx
          復(fù)制代碼
          import produce from 'immer'

          const [ list, setList ] = useState([{ price: 100, num: 1 }, { price: 200, num: 1 }])

          // 不使用用immer
          const onAdd = (index: number) => {
            /** 不使用immer */
            // const item = { ...list[index] }
            // item.num++
            // list[index] = item
            // setList([...list])

            /** 使用immer */
            setList(
              produce(darft => {
                darft[index].num++
              }),
            )
          }

          3. 可以用use-immer[19]簡化寫法:

          tsx
          復(fù)制代碼
          import useImmer from 'use-immer'

          const [ list, setList ] = useImmer([{ price: 100, num: 1 }, { price: 200, num: 1 }])

          const onAdd = (index: number) => {
            setList(darft => {
                darft[index].num++
            })
          }

          十一. 搭建npm私服

          公司前端項(xiàng)目不推薦使用太多第三方包,可以自己搭建公司npm私服,來托管公司自己封裝的狀態(tài)管理庫,請(qǐng)求庫,組件庫,以及腳手架clisdknpm包,方便復(fù)用和管理。

          可以看我這兩篇文章,都可以搭建npm私服:

          【前端工程化】巧用阿里云oss服務(wù)打造前端npm私有倉庫[20]

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

          十二. 各類型項(xiàng)目通用模版封裝

          可以提前根據(jù)公司的業(yè)務(wù)需求,封裝出各個(gè)端對(duì)應(yīng)通用開發(fā)模版,封裝好項(xiàng)目目錄結(jié)構(gòu),接口請(qǐng)求,狀態(tài)管理,代碼規(guī)范,git規(guī)范鉤子,頁面適配,權(quán)限,本地存儲(chǔ)管理等等,來減少開發(fā)新項(xiàng)目時(shí)前期準(zhǔn)備工作時(shí)間,也能更好的統(tǒng)一公司整體的代碼規(guī)范。

          1. 通用后臺(tái)管理系統(tǒng)基礎(chǔ)模版封裝
          2. 通用小程序基礎(chǔ)模版封裝
          3. 通用h5端基礎(chǔ)模版封裝
          4. 通用node端基礎(chǔ)模版封裝
          5. 其他類型的項(xiàng)目默認(rèn)模版封裝,減少重復(fù)工作。

          十三. 搭建cli腳手架下載模版。

          搭建類似vue-clivitecreate-react-app類的cli命令行腳手架來快速選擇和下載封裝好的模版,比git拉代碼要方便。

          具體cli腳手架的實(shí)現(xiàn)可以看我這篇文章:【前端工程化】從入門到精通,100行代碼構(gòu)建你的前端CLI腳手架之路[22]

          十四. git操作規(guī)范

          git操作規(guī)范也很重要,流程不規(guī)范很容易出現(xiàn)比較復(fù)雜的問題,要根據(jù)公司現(xiàn)有情況和業(yè)界比較好的實(shí)踐方案制定一套適合自己公司的git flow開發(fā)規(guī)范,用各種限制方案來避免出現(xiàn)問題,這個(gè)具體流規(guī)范后面會(huì)總結(jié)一篇文章出來。

          十五. 規(guī)范和使用文檔輸出文檔站點(diǎn)

          代碼規(guī)范和git提交規(guī)范以及各個(gè)封裝的庫使用說明要輸出成文檔部署到線上,方便新同事快速熟悉和使用。

          這個(gè)是很重要的,做了再多的基建和規(guī)范,如果沒有一個(gè)公共的文文檔來查閱,就沒辦法快速熟悉,所以要一個(gè)線上的規(guī)范文檔,把所有的規(guī)范都寫進(jìn)去,可以用語雀。

          作者:Ausra無憂
          鏈接:https://juejin.cn/post/7256393626682163237
          來源:稀土掘金

          前端 社群



          下方加 Nealyang 好友回復(fù)「 加群」即可。



          如果你覺得這篇內(nèi)容對(duì)你有幫助,我想請(qǐng)你幫我2個(gè)小忙:

          1. 點(diǎn)個(gè)「在看」,讓更多人也能看到這篇文章

          瀏覽 927
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲无码视频在线观看免费 | 欧美日韩一区二区三区四区论理片 | 狠狠操伊人| 日本一区三区祀频在线观看 | 91沈三级电影 |