文件夹结构
目录结构
├── config # umi 配置,包含路由,构建等配置
├── mock # 本地模拟数据
├── public
│ └── favicon.png # Favicon
├── src
│ ├── assets # 本地静态资源
│ ├── components # 业务通用组件
│ ├── e2e # 集成测试用例
│ ├── layouts # 通用布局
│ ├── models # 全局 dva model
│ ├── pages # 业务页面入口和常用模板
│ ├── services # 后台接口服务
│ ├── utils # 工具库
│ ├── locales # 国际化资源
│ ├── global.less # 全局样式
│ └── global.ts # 全局 JS
├── tests # 测试工具
├── README.md
└── package.json
功能组件目录结构
src
├── components
└── pages
├── Welcome // 路由组件下不应该再包含其他路由组件,基于这个约定就能清楚的区分路由组件和非路由组件了
| ├── components // 对于复杂的页面可以再自己做更深层次的组织,但建议不要超过三层
| ├── Form.tsx
| ├── index.tsx // 页面组件的代码
| └── index.less // 页面样式
├── Order // 路由组件下不应该再包含其他路由组件,基于这个约定就能清楚的区分路由组件和非路由组件了
| ├── index.tsx
| └── index.less
├── user // 一系列页面推荐通过小写的单一字母做 group 目录
| ├── components // group 下公用的组件集合
| ├── Login // group 下的页面 Login
| ├── Register // group 下的页面 Register
| └── util.ts // 这里可以有一些共用方法之类,不做推荐和约束,看业务场景自行做组织
└── * // 其它页面组件代码
安装包及构建
yarn
在package.json中scripts脚本中运行相关命令
例如
yarn start:stag
"start:stag": "cross-env REACT_APP_ENV=stag MOCK=none UMI_ENV=stag umi dev",
"start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=test umi dev",
"start:prod": "cross-env REACT_APP_ENV=prod MOCK=none UMI_ENV=prod umi dev",
主题和加载页
在config文件夹中defaultSettings.ts中配置主题和Logo
const settings: LayoutSettings & {
pwa?: boolean
logo?: string
} = {
// 修改右上角的 logo
logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
// 设置标题的 title
title: 'Ant Design Pro',
navTheme: 'light',
// 拂晓蓝
primaryColor: '#1890ff',
layout: 'mix',
contentWidth: 'Fluid',
fixSiderbar: true,
}
export default settings
在global.less导入自定义antd样式
@import '~antd/es/style/themes/default.less'
@import './antd-reset.less'
app入口
export { initialStateConfig, getInitialState } from './plugins/plugin-initial-state'
export { layout } from './plugins/plugin-layout'
获取用户信息的loading页面,根据缓存中的token的值请求用户信息接口,判断是否显示登录页面。
项目部分代码注释
const signInPath = '/user/sign-in'
/** 获取用户信息比较慢的时候会展示一个 loading */
export const initialStateConfig = {
loading: <PageLoading />,
}
/**
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state
* */
export const getInitialState = async (): Promise<{
settings?: Partial<LayoutSettings>
token?: string
organizationId?: string
currentUser?: API.CurrentUser
fetchUserInfo?: () => Promise<API.CurrentUser | undefined>
fetchUseOrgans?: () => Promise<API.CurrentUser | undefined>
}> => {
const token = lockr.get<string>(Lockr.TOKEN)
const fetchUserInfo = async () => {
try {
const data = await getCurrentUser()
return data
} catch (error) {
lockr.remove(Lockr.TOKEN)
history.push(signInPath)
return undefined
}
}
const fetchUseOrgans = async () => {
if (token) {
const data = await getUserOrganizes()
let list = []
//...
return list
}
return undefined
}
if (token) {
let currentUser = await fetchUserInfo()
const organs = await fetchUseOrgans()
currentUser = currentUser ? { ...currentUser, organizations: organs } : undefined
const cacheOrganizationId = lockr.get<string | null>(lockr.CacheKey.organizationId)
let organizationId = currentUser?.organizations?.[0]?.id
return {
fetchUserInfo,
organizationId,
token,
currentUser,
settings: defaultSettings,
}
}
return {
fetchUserInfo,
settings: defaultSettings,
}
}
在web启动后,获取基础的个人信息,并且把数据存入单例中,可以在之后的全局访问。
···
项目代码规范
Lint
禁止使用嵌套的三元表达式
👍推荐
var thing = foo ? bar : foobar
var thing
if (foo) {
thing = bar
} else if (baz === qux) {
thing = quxx
} else {
thing = foobar
}
不推荐
var thing = foo ? bar : baz === qux ? quxx : foobar
foo ? (baz === qux ? quxx() : foobar()) : bar()
setState的用法
👍推荐
this.setState((prevState) => ({ value: prevState.value + 1 }))
不推荐
setState({ value: this.state.value + 1 })
如果没有使用 state 应该使用 react function
👍推荐
const Foo = (props) => {
if (!props.foo) {
return null
}
return <div>{props.foo}</div>
}
不推荐
class Foo extends React.Component {
render() {
if (!this.props.foo) {
return null
}
return <div>{this.props.foo}</div>
}
}
使用一致的返回,如果有返回值应该每个 render 都提供返回值
👍推荐
function doSomething(condition) {
if (condition) {
return true
} else {
return false
}
}
不推荐
function doSomething(condition) {
if (condition) {
return true
} else {
return
}
}
使用解构赋值从 props 中取值
👍推荐
const MyComponent = (props) => {
const { id } = props;
return <div id={id} />
}
不推荐
const MyComponent = (props) => {
return <div id={props.id} />
}
每一行的代码不应超过80个字符
👍推荐
var foo = {
bar: 'This is a bar.',
baz: { qux: 'This is a qux' },
easier: 'to read',
}
不推荐
var foo = { bar: 'This is a bar.', baz: { qux: 'This is a qux' }, difficult: 'to read' }
不要对函数的参数进行直接操作
👍推荐
var foo = {
bar: 'This is a bar.',
baz: { qux: 'This is a qux' },
easier: 'to read',
}
不推荐
function foo(bar) {
bar++
}
hooks在函数最顶层使用
👍推荐
useEffect(function persistForm() {
if (name !== '') {
localStorage.setItem('formData', name)
}
})
不推荐
if (name !== '') {
useEffect(function persistForm() {
localStorage.setItem('formData', name)
})
}
TypeScript
变量、函数命名驼峰标识,要见文知义,通俗易懂
👍推荐
classCustomer {
private generationTimestamp: Date
private modificationTimestamp: Date
private recordId = '102'
}
不推荐
class DtaRcrd102 {
private genymdhms: Date // 你能读出这个变量名么?
private modymdhms: Date
private pszqint = '102'
}
参数、函数名更容易理解变量的含义
👍推荐
function between<T>(value: T, left: T, right: T) {
return left <= value && value <= right
}
不推荐
function between<T>(a1: T, a2: T, a3: T) {
return a2 <= a1 && a1 <= a3
}
常量的可易读性
👍推荐
const MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000
setTimeout(restart, MILLISECONDS_IN_A_DAY)
不推荐
setTimeout(restart, 86400000)
使用自解构的变量名
👍推荐
declare const users:Map<string, User>;
for (const [id, user] of users) {
// ...
}
不推荐
declare const users:Map<string, User>;
for (const keyValue of users) {
// ...
}
命名不要重复表达,过于累赘
👍推荐
type Car = {
make: string;
model: string;
color: string;
}
function print(car: Car): void {
console.log(`${this.make} ${this.model} (${this.color})`);
}
不推荐
type Car = {
carMake: string;
carModel: string;
carColor: string;
}
function print(car: Car): void {
console.log(`${this.carMake} ${this.carModel} (${this.carColor})`);
}
使用默认参数
👍推荐
function loadPages(count: number = 10) {
// ...
}
不推荐
function loadPages(count: number) {
const loadCount = count !== undefined ? count : 10;
// ...
}
参数越少越好,若有多个参数,请使用对象,采用解构的方式
- 当有人查看函数签名时,会立即清楚使用了哪些属性。
- 解构对传递给函数的参数对象做深拷贝,这可预防副作用。(注意:不会克隆从参数对象中解构的对象和数组)
TypeScript会对未使用的属性显示警告。
可以通过TypeScript的类型别名,进一步提高可读性
👍推荐
type MenuOptions = {title: string, body: string, buttonText: string, cancellable: boolean}
function createMenu(options: MenuOptions) {
// ...
}
createMenu(
{
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true
}
)
不推荐
function createMenu(title: string, body: string, buttonText: string, cancellable: boolean) {
// ...
}
createMenu('Foo', 'Bar', 'Baz', true)
使用Object.assign来设置默认对象
👍推荐
type MenuConfig = {title?: string, body?: string, buttonText?: string, cancellable?: boolean}
function createMenu(config: MenuConfig) {
const menuConfig = Object.assign({
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true
}, config);
}
createMenu({ body: 'MyBar' })
不推荐
type MenuConfig = {title?: string, body?: string, buttonText?: string, cancellable?: boolean}
function createMenu(config: MenuConfig) {
config.title = config.title || 'Foo'
config.body = config.body || 'Bar'
config.buttonText = config.buttonText || 'Baz'
config.cancellable = config.cancellable !== undefined ? config.cancellable : true
}
const menuConfig = {
title: null,
body: 'Bar',
buttonText: null,
cancellable: true
}
createMenu(menuConfig)
类型和接口,type和interface
当需要联合或是多个属性时,可以使用类型;如果需要给某个类或几个类,添加同一个方法,只是实现方法不一样,可以使用接口 👍推荐
type EmailConfig {
// ...
}
type DbConfig {
// ...
}
type Config = EmailConfig | DbConfig
interface Shape {
}
class Circle implements Shape {
// ...
}
class Square implements Shape {
// ...
}
不推荐
interface EmailConfig {
// ...
}
interface DbConfig {
// ...
}
interface Config {
// ...
}
//...
type Shape {
// ...
}
函数的粒子化,一个函数做一件事
👍推荐
function emailClients(clients: Client) {
clients.filter(isActiveClient).forEach(email)
}
function isActiveClient(client: Client) {
const clientRecord = database.lookup(client)
return clientRecord.isActive()
}
不推荐
function emailClients(clients: Client) {
clients.forEach((client) => {
const clientRecord = database.lookup(client)
if (clientRecord.isActive()) {
email(client)
}
})
}
删除重复代码,以代码简洁为大道
👍推荐
class Developer {
// ...
getExtraDetails() {
return {
githubLink: this.githubLink,
}
}
}
class Manager {
// ...
getExtraDetails() {
return {
portfolio: this.portfolio,
}
}
}
function showEmployeeList(employee: Developer | Manager) {
employee.forEach((employee) => {
const expectedSalary = developer.calculateExpectedSalary()
const experience = developer.getExperience()
const extra = employee.getExtraDetails()
const data = {
expectedSalary,
experience,
extra,
}
render(data)
})
}
不推荐
function showDeveloperList(developers: Developer[]) {
developers.forEach((developer) => {
const expectedSalary = developer.calculateExpectedSalary()
const experience = developer.getExperience()
const githubLink = developer.getGithubLink()
const data = {
expectedSalary,
experience,
githubLink
}
render(data);
})
}
function showManagerList(managers: Manager[]) {
managers.forEach((manager) => {
const expectedSalary = manager.calculateExpectedSalary()
const experience = manager.getExperience()
const portfolio = manager.getMBAProjects()
const data = {
expectedSalary,
experience,
portfolio
}
render(data)
})
}
当然,如果是两个不同的业务之间有一些重复的代码功能,为了维护代码块结构的简单性,可以考虑用独立代码的重复,这样在维护某个业务的时候,可以不用考虑另一个因改代码而带来的影响。
HTML
所有具有开始标签和结束标签的元素都要写上起止标签
👍推荐
<img src='' /> <br/> <input />
不推荐
<img src=''> <br> <input>
段落元素与标题元素只能嵌套内联元素
👍推荐
<h1> <span></span> </h1>
不推荐
<p>第一行<br/>第二行</p>
HTML标签名、类名、标签属性和大部分属性值统一用小写
👍推荐
<div class="demo"></div>
不推荐
<div class="DEMO"></div>
不需要为 CSS、JS 指定类型属性,HTML5 中默认已包含
👍推荐
<link rel="stylesheet" href="" >
<script src=""></script>
不推荐
<link rel="stylesheet" type="text/css" href="" >
<script type="text/javascript" src="" ></script>
单行注释
👍推荐
<!-- Comment Text -->
<div>...</div>
不推荐
<div>...</div><!-- Comment Text -->
<div><!-- Comment Text -->
...
</div>