从痛点出发封装项目开发模板
本文首发于:github.com/bigo-fronte… 欢迎关注、转载。
前言
痛点,是指尚未被满足的,而又被广泛渴望的需求,英文名叫pain point。
在做技术规划或者是OKR的时候,往往会问自己一个问题:当前阶段的主要痛点是什么?然后针对痛点,寻求解决方案,最后就能得出较为实际的阶段性目标。
显然,从痛点出发,可以让付出得到的收益更高。
主要存在的问题
目前我们团队主要负责内部系统的开发,基本都是管理后台类型的应用。不同系统之间,没有统一的开发模板,甚至技术栈都是不一样的。团队成员间进行支援开发时,往往需要较多的时间熟悉项目才能上手开发。这在一定程度上影响了开发效率和团队的技术沉淀。因此,我们基于react+redux+ts技术栈,封装了统一的项目开发模板,其中,根据平时开发的痛点,集成了较多实用的工具。
服务端接口类型编写
使用ts进行开发,人类高质量程序员难免会写很多interface。对于众多的后端接口,每个接口请求参数、响应数据都一个一个去编写,难免有点崩溃。晒干了沉默,悔得很冲动~于是着手去寻找是否有相关的工具可以方便的转换。
- json2ts:这是一个可以将json数据转换成TypeScript interfaces的工具。使用这个工具,需要重复的复制粘贴,不是很理想。
- auto-service:根据 Swagger 或者 YApi 格式的接口文档(JSON)自动生成TypeScript的接口调用或者类型代码。这个工具依赖Java环境,所以需要确保你的电脑已经安装了Java。
- swagger-typescript-api:支持Swagger scheme生成TS类型及接口调用方法。笔者在尝试的过程中,发现生成出来的代码有些错误,排查许久,不知是不是后端生成的swagger数据有问题,最终也并未解决。
- yapi-to-typescript:根据 YApi 或 Swagger 的接口定义生成TypeScript或JavaScript的接口类型及其请求函数代码。生成的代码可读性较好,同时支持生成 React Hooks 的请求代码。
我们内部使用了YApi进行接口的管理,故最后选择了yapi-to-typescript
。官网文档较为完善,配置也简单易用,这里对yapi-to-typescript
的使用不做过多描述。这里提一下使用yapi-to-typescript
的一些注意事项:
- 生成的数据源于yapi,所以应该和后端达成一致,严格按照格式进行api的添加。
- 每次生成都会直接覆盖生成的文件,故需要注意,不要在生成的文件里面做任何修改,避免下次生成的时候被覆盖。
- 多人合作的时候,在生成api代码的时候,注意生成前后的diff,是否有涉及其他模块的更改。可通过配置
preproccessInterface
过滤一些不想重新生成的接口。 - 生成的请求函数名可自定义,建议使用请求路径驼峰处理作为请求方法名,以保证没有重名。如get /api/v1/user生成getApiV1User。
Redux管理
说到react,难以避免要谈谈redux。react和redux可以说是一对好基友了,曾被认为是大规模React应用中管理状态的最佳组合。
然而在使用redux的过程中,总有一些痛苦。常见的困扰包括:
- 配置复杂
- 模板代码太多。为了使用redux,我们不得不添加action,还有一堆常量。
- 需要添加很多依赖包,如redux-thunk
redux官方也意识到了这些问题,正所谓前端何苦为难前端,于是推出了工具包redux-toolkit
The official, opinionated, batteries-included toolset for efficient Redux development
redux-toolkit通过内置的插件和代码封装,让使用者可以更加方便地使用redux,并且有较好的代码组织。
redux-toolkit包含了如下API:
- configureStore(): 包装createStore以提供简化的配置选项和良好的默认预设。它可以自动组合你的切片 reducers,添加您提供的任何 Redux 中间件,默认情况下包含 redux-thunk ,并允许使用 Redux DevTools 扩展。
- createReducer(): 为 case reducer 函数提供 action 类型的查找表,而不是编写switch语句。此外,它会自动使用immer 库来让您使用普通的可变代码编写更简单的 immutable 更新,例如 state.todos [3] .completed = true 。
- createAction(): 为给定的 action type string 生成一个 action creator 函数。函数本身定义了 toString(),因此它可以用来代替 type 常量。
- createSlice(): 接受一个 reducer 函数的对象、分片名称和初始状态值,并且自动生成具有相应 action creators 和 action 类型的分片reducer。
- createAsyncThunk: 接受一个 action type string 和一个返回 promise 的函数,并生成一个发起基于该 promise 的pending/fulfilled/rejected action 类型的 thunk。
- createEntityAdapter: 生成一组可重用的 reducers 和 selectors,以管理存储中的规范化数据
- createSelector 组件 来自 Reselect 库,为了易用再导出。
在具体项目中,如何设置和使用 Redux Toolkit?
- 使用 configureStore 创建 Redux 存储
//configureStore 接受一个 reducer 函数作为命名参数
//configureStore 自动使用默认设置加载store
export const store = configureStore({
reducer: {},
})
- 为 React 应用提供 store
// 使用<Provider>包装你的<App />组件
// 将store作为prop传递给Provider
<Provider store={store}>
<App />
</Provider>
- 使用 createSlice 创建“切片” reducer
//使用字符串名称、初始状态和命名的 reducer 函数调用 createSlice
//导出生成的 slice reducer 和 action
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
export interface CounterState {
value: number
}
const initialState: CounterState = {
value: 0,
}
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.value += 1
},
decrement: (state) => {
state.value -= 1
},
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
},
},
})
// Action creators are generated for each case reducer function
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer
- 在 React 组件中使用 useSelector/useDispatch 钩子
//使用 useSelector 钩子从store中读取数据
//使用 useDispatch 钩子获取dispatch函数,并根据需要dispatch action
import React from 'react'
import { RootState } from '../../app/store'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from './counterSlice'
export function Counter() {
const count = useSelector((state: RootState) => state.counter.value)
const dispatch = useDispatch()
return (
<div>
<div>
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
Increment
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
Decrement
</button>
</div>
</div>
)
}
更详细的文档>>>传送门
模板生成
在实际的开发过程中,我们经常要新建组件、页面或者上面提到的redux slice。这些基本上都有较为固定的结构,比如新建组件都要import react, 然后export 组件。这时候,如何能快速在指定文件目录下生成对应的文件?
Plop就要登场了
Micro-generator framework that makes it easy for an entire team to create files with a level of uniformity.
先看一下动图,了解一下它的效果:
Plop提供了一种以一致方式生成代码或任何其他类型的纯文本文件的简单方法。
当需要创建新的组件时,在代码库中找到代表当前“最佳实践”的文件并不总是那么容易。 这就是Plop发挥作用的时候。 有了plop,就有了在开发过程中创建任何给定模式的“最佳实践”方法。通过plop的交互式命令行, 我们就可以轻松生成代码。 这不仅可以免于在代码库中四处寻找要复制的正确文件,而且还将“正确的方式”转变为“最简单的方式”来创建新文件。
配置起来也是比较简单,在你的项目根目录新建plopfile.js
文件,最基本的使用如下:
module.exports = function (plop) {
// controller generator
plop.setGenerator('controller', {
description: 'application controller logic',
prompts: [{
type: 'input',
name: 'name',
message: 'controller name please'
}],
actions: [{
type: 'add',
path: 'src/{{name}}.js',
templateFile: 'plop-templates/controller.hbs'
}]
});
};
prompts
用来配置命令行交互,获取需要用户提供的信息。
actions
是用来配置要做什么事情,比如使用某个模板在生成指定的文件。
更多配置见文档:传送门
自定义hooks
我们都知道,通过React Hooks,可以将组件的逻辑提取到可重用的函数中作为自定义 Hooks。业界也有很多优秀的hook库,比如react-use、ahooks等。
在我们目前的开发模板中,引入了ahooks,其中很多封装好的hook都能让我们开箱即用。当然,如果你需要自己造一些轮子,不妨也借鉴一下其中的实现。
ahooks的useRequest
结合yapi-to-typescript,可以让我们的api调用如德芙一般纵享丝滑;useAntdTable
更是让我们使用antd table的时候更加舒畅。有需要的不妨试试。
结语
古人有云:君子生非异也,善假于物也。
通过集成一些好用的工具,可以让我们在开发过程中大大提高开发效率,把时间专注于具体的业务实现。
我们不再关心api相关代码的生成,只要后端在yapi平台上面录入接口,然后我们在项目中yarn ytt
即可生成对应的类型定义和接口调用方法;我们也不再去纠结如何组织文件结构,使用plop就可以生成文件模板。
当然,每个团队都有自己的开发模式,从痛点出发,找到合适自己的才是最好的。
以上,是我们在统一开发模板过程中一些总结。欢迎指正!
欢迎大家留言讨论,祝工作顺利、生活愉快!
我是bigo前端,下期见。
转载自:https://juejin.cn/post/7121555940255465503