└── README.md /README.md: -------------------------------------------------------------------------------- 1 | ### Why 🤔 2 | 之前在字节的时候 看过剪映团队负责人 曾琛 写的 剪映 性能优化,超脱于市面上所有的专栏,确实很佩服,是真正的沉淀出了经验,故在此沉淀一下我的团队和业务经验
3 | 一部分是学习来自于开源社区巨佬们的技术经验和优秀编码习惯,另一部分是沉淀出自己待过的四个厂百度、支付宝、字节、腾讯,自己觉得最舒服的 FE TEAM 和 FE Development Engineer 的业务能力标准和经验 4 | ### Stardard ✨ 5 | 一个职级在 2-1 到 2-2 之间(腾讯 t9)的前端,我觉得应该具备以下能力:
6 | 1.owner 稳定 - 需求评审、风险把控、技术优化、方案调研确定、技术评审、推动改良、能够独立 onwer 一个复杂的业务项目,稳定完成需求
7 | 2.带人 - 定制团队规范、带小型前端团队/带新人、需求分配、把控需求交付流程
8 | 3.关注技术 - 指无论有什么样的需求,脑子里都有一套对应的方案流程
9 | 一个很明显的区别就是,很多前端甚至连 Github 都不能熟练使用,更不能指望调研给出优秀的技术方案 10 | 11 | ## Front End Team Requirements(前端团队要求) 12 | #### Collaborative Development Specification(协同开发规范) 13 | - 如果非开源项目 or 注重 git commit history 的美观,请勿使用 squash merge,防止找不到对应的协同开发者 14 | - 如果想要提交代码到协同者的分支 or 火车分支,请提交 merge request,复制 mr link 给对方,为保证代码质量,可先 CR + approve,请勿直接 push 15 | #### Engineering Experience - Team Oriented(工程经验 - 团队导向) 16 | - 业务中尽量避免使用 //@ts-ignore //@ts-noCheck,过不去的 Lint,要么是技术不到位要么是懒 17 | - ESlint Prettier 与 VScode Workspace
18 | 这里是在腾讯的时候,发现的一个工作流问题,有一些 Pipe Line 会阻止 warning 的lint error,但是我本地没有 Eslint,只有 Prettier,导致无法在开发阶段去发现并且自动 Ctrl+S 去修复
19 | 最佳实践:ESlint 在开发阶段自动读取 WorkSpace 中的配置并且 Ctrl+S 会自动去 fix 这些 warning
20 | 把 Prettier 变成 Eslint 的检测中 用来规范代码风格 21 | ```bash 22 | prettier eslint-config-prettier eslint-plugin-prettier 23 | ``` 24 | ```json 25 | // .eslintrc.json 26 | { 27 | "extends": [ 28 | "next/core-web-vitals", 29 | "eslint:recommended", 30 | "plugin:prettier/recommended" 31 | ], 32 | "plugins": ["prettier"], 33 | "rules": { 34 | "prettier/prettier": "error" 35 | }, 36 | "ignorePatterns": ["node_modules/", ".next/", "out/", "public/",".eslintrc.json"], 37 | } 38 | 39 | // .prettierrc 40 | { 41 | "singleQuote": false, 42 | "semi": true, 43 | "trailingComma": "all", 44 | "jsxSingleQuote": false 45 | } 46 | ``` 47 | ```json 48 | // .vscode/settings.json 49 | { 50 | "eslint.validate": [ 51 | "javascript", 52 | "typescript", 53 | "javascriptreact", 54 | "typescriptreact", 55 | "vue", 56 | "json", 57 | "jsonc", 58 | "json5" 59 | ], 60 | "stylelint.validate": [ 61 | "css", 62 | "less", 63 | "scss" 64 | ], 65 | "editor.codeActionsOnSave": { 66 | "source.fixAll": true 67 | } 68 | } 69 | ``` 70 | - 非开源仓库请务必 lock,防止某天 Pipe Line 构建完成后,线上崩溃,以及被投毒
71 | - 官网开发首选 amis,参考 Antv 官网、AntDesign 官网、CapCut 官网等 (首先自己公司的框架,我一般用 Dumi)
72 | 优点:
73 | 1.后续迭代便利,开发难度小
74 | 2.只需修改 Config 即可更换网站信息
75 | 3.静态站点的 SSG
76 | 4.有问题随时可以找 [@Peach老师](https://github.com/PeachScript)(🤪 77 | - wasm 不是必须品,应用场景较少,主要用于:
78 | 1.大规模计算,视频、绘制、引擎 如 Antv 可视化渲染、视频团队、抖音烟花特效等
79 | 2.突破原有 SDK 的性能瓶颈 如 字节电商 IM SDK、监控 SDK
80 | 3.增加被反编译的成本 如 字节风控 SDK
81 | - umi3 升 4 比较容易,3 已经不维护,不会 onCall 和 新 Feature,但市面上很多团队还在使用
82 | 收益:
83 | 1.构建速度 以及 Hot Module update 显著提高
84 | 2.新特性以及可以及时被响应的 Github issue
85 | 3.毕竟咱也是 umi 团队成员,基本不会有无法解决的问题
86 | 缺点:需要一定的人力投入 以及 全量回归 成本
87 | - 巨型 Monorepo 仓库不同的 Pkg 下的依赖版本冲突 以及 老仓库巨石应用 某个基层包 的升级 都可以通过 Resolution 去解决
88 | 前者场景表现为项目崩溃、后者场景多数为安全合规 包版本卡点(在字节、腾讯都解决过类似的问题) 89 | - 大厂以及开源项目的国际化:
90 | 原理:其实都是 react-i18n,比较简单的去取对应 lang key 下的 value
91 | 基建团队做了什么?
92 | 答:参考 Element-plus 的国际化,通过一个第三方的翻译平台,方便翻译人员操作,同时生成对应的 JSON 文件,只不过大厂不是第三方的服务,而是有自己的国际化翻译平台
93 | 所以如果未来去初创/中小公司带团队,海外业务国际化的技术选型可以使用第三方的服务 94 | - 用户行为分析的埋点一般是 PM 提需求,在哪里埋,数据分析需要什么指标,前端埋点比较简单,一般只需要带上对应的 Event key value 95 | - 性能监控指标 C 端业务会看重这个 一般 X-FMP FCP 会比较重要一点 96 | - 大量动态加载的静态资源,图片、gif、视频、字体等,都可以通过 InDexDB 去做一层 Cache,有效提高请求速度 97 | - 合理使用 preconnect、prefetch、preload 98 | - git commit message 请一定带上 type 信息,如果需要带上 emoji(一般是开源项目),请遵循 [git message emoji 标准](https://github.com/liuchengxu/git-commit-emoji-cn) 99 | ## Individual Front End Development Competency Requirements(前端开发工程师能力要求) 100 | #### Hooks
101 | - 与 React 原生 Hook 写法保持一致 小驼峰,返回 State 与 UpdateState 102 | ```tsx 103 | const useCpData = () => { 104 | ... 105 | return [cpData, setCpData] 106 | } 107 | ``` 108 | 109 | - Hook 与 Modal State 110 | 在一些情况下 在 Hook 中处理 Modal 中的状态去复用极为方便 111 | 举个例子 有大大小小 10 个模块,且可以单独通过 url 进入,此时 Modal 中不一定存在对应的 State Value,但每个页面都需要这个 Modal State
112 | > 优点: 113 | > - 只负责 Save Init State Or Get State from Modal,脱离业务逻辑 114 | > - 逻辑复用极其简单,无需在每个模块去 Init State 115 | ```tsx 116 | const useUserInfo = (props) => { 117 | const userInfo = useRedux(state: GlobalModal => state.Modal.userInfo) 118 | const setUserInfo = useRedux(state: GlobalModal => state.Modal.setUserInfo) 119 | 120 | const fetchUserInfo = () => { 121 | dispatch(params) // saveState to Modal 122 | } 123 | 124 | useEffect(() => { 125 | if(!userInfo){ 126 | fetchUserInfo() 127 | } 128 | },[]) 129 | ... 130 | return [userInfo, setUserInfo] 131 | } 132 | ``` 133 | - 功能型 Hook 不与业务数据相关联,请提取出 @TEAM/utils 第三方 PKG 134 | 如 useEventOutSide 这种 135 | 136 | Tips: Meta 团队提供了非常多的 Hooks,但是很多 Hooks 都是为 SSR 服务的,一般的业务开发都无需关心 137 | #### Component
138 | - 组件命名 - 大驼峰 文件名:大驼峰 or 连接符 139 | - 类型声明原则 组件大驼峰 + Props 140 | > 标准 tsx 组件 141 | ```tsx 142 | // pages/Login/LoginModal/index.tsx 143 | interface LoginModalProps { 144 | ... 145 | } 146 | const LoginModal: FC = () => <> 147 | ... 148 | 149 | export default LoginModal; 150 | ``` 151 | - 组件事件以及回调函数命名应遵循 onEvent => handleEvent 152 | - 业务组件 - 推荐 Data(数据) 与 View(视图) 分离,用相应的 Data Hook 去 管理/创建 CP 单例
153 | 举个例子,这里有一个商业化复杂应用场景下的 Table Modal, 虽然复杂 但是极其耐用
154 | 具体的功能大概是:
155 | 复杂业务场景的 Search + 后端分页 + 复杂业务表格数据以及嵌套多重数据 + 自定义 UI 插槽 + 以及各种 Modal Render Props 156 | ```tsx 157 | // 通用组件 非 Page CP 158 | // components/TableModal/index.tsx 159 | interface TableModalProps{ 160 | records: TableRecords; 161 | columns: TableColumns; 162 | pagination: PaginationInfo; 163 | searchProps: TeamSearchCpProps, 164 | ... 165 | } 166 | 167 | const TableModal:FC = () => { 168 | return <> ... 169 | } 170 | 171 | export default TableModal; 172 | // components/TableModal/hooks/useTableModal.ts 173 | const useTableModal = () => { 174 | const [data, setData] = useState([]) 175 | const [pagination, setPagination] = useState({ pageIndex: 1, pageSize }); 176 | const [tableStatus, setTableStatus] = useState(TableStatus.empty) 177 | ... Hook with Data 178 | return [data, pagination, tableStatus, setData, setPagination, setTableStatus] 179 | } 180 | 181 | // Usage 伪代码 182 | const Component: FC = () => { 183 | const [data, pagination, tableStatus, setData, setPagination, setTableStatus] = useTableModal() 184 | return <> 185 | 191 | ... 192 | 193 | } 194 | ``` 195 | 196 | #### Modal State 197 | - 去不同公司不同团队写 React,必须要适应的是不同的`状态管理`
198 | 基于 Redux 的 Dva、easy-xxx 等,Mobx、zustand、jotai等
199 | 我本人在过的团队使用的基本上都是围绕 redux 理念的生态,但是现在有不少团队在使用其他的状态管理工具
200 | - 以页面为维度去划分 Namespace 其实是有点混乱的,如Dva,本质上还是全局的状态
201 | 个人推荐 全局状态划分不同的顶层 Modal,易于区分不同的数据模型、清晰易懂、容易定位
202 | 如项目量级过大,顶级 Modal 确实会出现过多的问题,不过切忌顶层全局 Modal 与 页面级/组件级 混用,会产生交接成本 203 | #### 模块 204 | - 导入顺序优先级
205 | 1.第三方 Pkg
206 | 2.本地 JS 模块
207 | 3.非 JS 模块
208 | - 类型导入最好带上 type 关键字 209 | - 类型声明放在 ./type.ts 210 | > 正确示例✅ 211 | ```ts 212 | import { Cp1, Cp2, ... } from 'antd'; 213 | import { xxx } from 'umi; 214 | 215 | import MyComponent from './MyComponent'; 216 | import useHook from './hooks/useHook'; 217 | 218 | import type { myType} from './type.ts'; 219 | import styles from './index.less'; 220 | ``` 221 | #### Proven coding experience(成熟编码经验) 222 | - 避免默认导出与具名导出同时使用,会导致意外的 Bundle 体积增大(特殊情况除外 如语义化的附属组件 参考 AntDesign Form) 223 | ```tsx 224 | // 错误示例❌ 225 | export { utilA, utilB }; 226 | export default Utils; 227 | ``` 228 | - 习惯使用枚举 (你也不想你的代码里面一堆看不懂的 数字/字符串 来表示业务逻辑吧 1001是什么 1002是什么 1003是什么???)
229 | 优点:
230 | 1.维护十分方便,方便对应 Value 统一修改
231 | 2.语义化、同一种类枚举类型业务逻辑更加清晰
232 | 3.抽离复用
233 | ```ts 234 | // 错误示例❌ 235 | interface UserInfo { 236 | type: 'normal' | 'admin' | 'onwer' 237 | } 238 | ... 239 | if(res === 'normal') { ... } 240 | ``` 241 | ```ts 242 | // 正确示例✅ 243 | export enum UserType { 244 | NORMAL = 'normal', 245 | ADMIN = 'admin', 246 | OWNER = 'owner' 247 | } 248 | ... 249 | if(res === UserType.OWNER) { ... } 250 | ``` 251 | - 避免频繁使用 useMemo、useCallback 252 | - useMemo、useCallback 依赖超过 5 个,去掉 Hook,避免增加心智负担 253 | - ⭐️⭐️⭐️ 请将复杂业务/计算逻辑 统一抽离放在 useMemo 中 254 | ```ts 255 | // 组件设计场景 256 | const mergedXXX = useMemo(()=>{ 257 | const XXX = ... // prop 258 | return context.XXX ?? XXX 259 | },[dep1, dep2, ...) 260 | 261 | // 业务场景 262 | const mergedTableData = useMemo(()=>{ 263 | const XXX = ... 264 | ... //复杂业务逻辑 265 | ... //复杂业务逻辑 266 | return XXX 267 | },[selectedData, mergedColumns, ...) 268 | ``` 269 | - 避免三元表达式嵌套 270 | - 注意空格,养成习惯,包括纯代码以及中英文混合使用 271 | ```ts 272 | // 这是一个关于业务 Code 的注释 273 | ``` 274 | - 在公司代码中,尽量使用 if 条件语句替代可扩展的可选链逻辑 ? (看似麻烦 好处是代码易读懂且方便后续扩展) 275 | > 正确 ✅ 276 | ```ts 277 | interface UserInfo { 278 | [key: string]: string 279 | } 280 | const res: { 281 | userInfo: UserInfo 282 | } = dispatch(); 283 | 284 | if (res?.useInfo) { 285 | console.log(res.useInfo.age); 286 | } 287 | ``` 288 | - 调用不一定存在的函数,请记得 fn?.(),一般用于组件封装场景,以及数据通信 289 | --------------------------------------------------------------------------------