【干貨】被裁員前,我為公司做的15個(gè)前端基建分享~
大廠技術(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,組件,頁面,路由,hooks,store,不管是全局使用到的,還是單獨(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ī)范
-
vscode[1]:統(tǒng)一前端編輯器。 -
editorconfig[2]: 統(tǒng)一團(tuán)隊(duì)vscode編輯器默認(rèn)配置。 -
prettier[3]: 保存文件自動(dòng)格式化代碼。 -
eslint[4]: 檢測代碼語法規(guī)范和錯(cuò)誤。 -
stylelint[5]: 檢測和格式化樣式文件語法
可以看我這篇文章:【前端工程化】配置React+ts企業(yè)級(jí)代碼規(guī)范及樣式格式和git提交規(guī)范[6]
git提交規(guī)范
-
husky[7]:可以監(jiān)聽githooks[8]執(zhí)行,在對(duì)應(yīng)hook執(zhí)行階段做一些處理的操作。 -
lint-staged[9]: 只檢測暫存區(qū)文件代碼,優(yōu)化eslint檢測速度。 -
pre-commit[10]:githooks之一, 在commit提交前使用tsc和eslint對(duì)語法進(jìn)行檢測。 -
commit-msg[11]:githooks之一,在commit提交前對(duì)commit備注信息進(jìn)行檢測。 -
commitlint[12]:在githooks的pre-commit階段對(duì)commit備注信息進(jìn)行檢測。 -
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)管理
用react的context封裝了一個(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ì)localStorage和sessionStorage還有cookie簡單封裝一下,封裝后使用的好處:
-
自動(dòng)序列化,存儲(chǔ)的時(shí)候轉(zhuǎn)字符串,取得時(shí)候再轉(zhuǎn)回來。 -
類型自動(dòng)推斷,在實(shí)例化的時(shí)候傳入類型,在設(shè)置和獲取值的時(shí)候都會(huì)自動(dòng)類型推斷。 -
可以統(tǒng)一管理,把本地存儲(chǔ)都放在一個(gè)文件里面,避免后期本地存儲(chǔ)混亂不好維護(hù)問題。 -
抹平平臺(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ù)用,使用上比較麻煩,每次需要傳GET和POST類型,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ù)用,一般可以分為以下幾種組件:
-
UI組件 -
業(yè)務(wù)組件 -
功能組件:上拉刷新,滾動(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 scoped, react使用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)求庫,組件庫,以及腳手架cli,sdk等npm包,方便復(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ī)范。
-
通用后臺(tái)管理系統(tǒng)基礎(chǔ)模版封裝 -
通用小程序基礎(chǔ)模版封裝 -
通用h5端基礎(chǔ)模版封裝 -
通用node端基礎(chǔ)模版封裝 -
其他類型的項(xiàng)目默認(rèn)模版封裝,減少重復(fù)工作。
十三. 搭建cli腳手架下載模版。
搭建類似vue-cli, vite, create-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è)小忙:
