欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

参考一下我们公司的前端代码规范(JS+TS + React)

最编程 2024-08-03 16:20:33
...

前端代码规范(TS + React)

背景:为使团队代码风格尽量统一和规范,方便cr和查找问题,结合过去一段时间的经验,针对react + typescript提出代码规范,后续在实践过程中可持续优化此规范。

javascript规范

使用单引号或es6反引号,使用两个空格作为缩进

句末使用添加分号(buchongzidongtinajia)

前端代码是否只使用camelCase?(建议)

若转换前后端字段,可添加拦截器:

import camelcaseKeys from 'camelcase-keys';
import snakeCaseKeys from 'snakecase-keys';

const handleResponse = (response: AxiosResponse): AxiosResponse => {
 return camelcaseKeys(response.data, { deep: true }) as unknown as AxiosResponse;
}

export const handleRequest = (request: AxiosRequestConfig): AxiosRequestConfig => {
 if (request.data) {
   request.data = snakeCaseKeys(request.data , { deep: true });
 }
 if (request.params) {
   request.params = snakeCaseKeys(request.params , { deep: true });
 }
 return request;
}

export const productFeedsBaseURL = '/api';

export const productFeedsRequest: AxiosInstance = Axios.create({
 // baseURL: `http://www.freehard.top${productFeedsBaseURL}`,
 baseURL: '/api',
 withCredentials: true
});

productFeedsRequest.interceptors.request.use(handleRequest);

productFeedsRequest.interceptors.response.use(handleResponse, handleErr);

空格

二元和三元运算符两侧必须有一个空格,一元运算符与操作对象之间不允许有空格。

用作代码块起始的左花括号 { 前必须有一个空格。

if / else / for / while / function / switch / do / try / catch / finally 关键字后,必须有一个空格。

在对象创建时,属性中的 : 之后必须有空格,: 之前不允许有空格。

圆括号内侧不留空格;圆括号内的大括号前后,冒号和分号后空一格

单个组件文件代码不超过400行,超过时考虑拆分组件;单函数代码不超过50行

代码注释

单行注释//与注释内容之间空一格 //bad commment // good comment

TODO FIXME: 大写并在冒号后与内容之间空一格,建议配置VSCode插件TODO Highlight使用。(建议)

避免拼写错误,建议使用VSCode 插件 Code Spell Checker(建议)

模块导入

优先导入第三方模块(node_modules 中的),再引入当前项目组件,;第三方模块导入代码段和业务模块导入代码段之间空一行,import 代码段与之后的业务代码之间空一行。

import React, { PureComponent } from 'react'
import { Checkbox, Button, Card, Spin } from 'antd'

import VoiceCollect from './VoiceCollect'
import { AudioPlayerProvider } from '../../components/AudioPlayer'
import * as API from '../../api'
import styles from './index.scss'
import { tips } from '../../common/tools'
import { autoCatch } from '../../decorators'

const CheckboxGroup = Checkbox.Group

导入路径过长时使用webpack配置别名

没有使用到的模块和变量应该删除

优先使用const声明,const > let > var

换行操作符前置(建议)

React规范

基本规则

每个文件只包含一个React组件 无状态组件允许一个文件包含多个组件

命名

文件名:使用PascalCase命名。eg: ReservationCard.jsx, ReservationCard.tsx

引用命名:React组件使用PascalCase命名,实例使用camelCase命名

组件命名:组件名称应该和文件名一致。如果在目录中的组件应该使用index.jsx/index.tsx作为文件名,并使用文件夹名称作为组件名。

其他非组件的小写命名(redux, service)

// bad
import reservationCard from './ReservationCard';

// good
import ReservationCard from './ReservationCard';

// bad
const ReservationItem = <ReservationCard />;

// good
const reservationItem = <ReservationCard />;

// bad
import Footer from './Footer/Footer';

// bad
import Footer from './Footer/index';

// good
import Footer from './Footer';

对齐

多行属性采用缩进,属性可以放在一行就保持在一行中

// bad
<Foo superLongParam="bar"
     anotherSuperLongParam="baz" />

// good
<Foo
  superLongParam="bar"
  anotherSuperLongParam="baz"
/>

// 如果组件的属性可以放在一行就保持在当前一行中
<Foo bar="bar" />

引号

JSX字符串属性采用双引号,其他采用单引号

// bad
  <Foo bar='bar' />

// good
  <Foo bar="bar" />

// bad
  <Foo style={{ left: "20px" }} />

// good
  <Foo style={{ left: '20px' }} />

空格

始终在自闭合标签前面添加一个空格

// bad
<Foo/>

// very bad
<Foo                 />

// bad
<Foo
 />

// good
<Foo />

属性

属性名称始终使用camelCase

属性值为true时,省略改属性的赋值

属性前后不要空格

// bad
<Foo
  UserName="hello"
  phone_number={12345678}
  hidden={true}
  loading={ loading }
/>

// good
<Foo
  userName="hello"
  phoneNumber={12345678}
  hidden
  loading={loading}
/>

括号

使用括号包裹多行JSX标签

// bad
render() {
  return <MyComponent className="long body" foo="bar">
           <MyChild />
         </MyComponent>;
}

// good
render() {
  return (
    <MyComponent className="long body" foo="bar">
      <MyChild />
    </MyComponent>
  );
}

// good, when single line
render() {
  const body = <div>hello</div>;
  return <MyComponent>{body}</MyComponent>;
}

标签

当标签没有子元素时,始终使用自闭合标签

多行属性时,关闭标签应另起一行

// bad
<Foo className="stuff"></Foo>

// good
<Foo className="stuff" />
// bad
<Foo
  bar="bar"
  baz="baz" />

// good
<Foo
  bar="bar"
  baz="baz"
/>

class组件成员排布顺序

按照 普通成员, state,生命周期函数,自定义函数,render函数的顺序编写.

class SameCheck extends PureComponent<Prop, State> {
  algorithms: Algorithm = []
 
  readonly state: State = {
       sampleA: '',
       sampleB: '',
       algorithms: [],
       submiting: false,
       result: [],
       audioBlob: null,
       formIniting: false
   }

   componentDidMount() {
       this.getInfo()
   }

   handleVoiceAChange = (voiceKey: string) => {
       this.setState({ sampleA: voiceKey })
   }
   render() {}
}

使用React.Fragment

// bad
<React.Fragment>
   <div></div>
   <h3></h3>
</React.Fragment>
// good
<>
   <div></div>
   <h3></h3>
</>

使用箭头函数,不使用bind

// good
handleClick = () => { }
// bad
handleClick() { }
<div onClick={this.handleClick.bind(this)}></div>

不允许直接修改state, state应该只读,使用setState修改

// bad
this.state.arr.push(xxx)
this.forceUpdate()
// good
this.setState({ arr: this.state.arr.concat([xxx]) })

避免内存泄漏,组件销毁时清除注册的事件,定时器。

尽量使用函数式组件配合hooks,减少使用class组件(建议)

只在最顶层使用hook,不要在循环,条件或嵌套函数中调用Hook

推荐使用eslint插件 eslint-plugin-react-hooks

{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
    "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
  }
}

useEffect的使用注意事项

可参考资料:

useEffect完全指南

使用hooks请求数据

react hooks依赖实践指南

避免陷入死循环

当effect中更新了state时会引起组件重新渲染,组件重新渲染又会出发effect,无限循环。

给useEffect第二个参数传入[],只在组件mount的时候触发effect。

// bad 
function App() {
 const [data, setData] = useState({ hits: [] });
 useEffect(async () => {
   const result = await axios(
     'https://hn.algolia.com/api/v1/search?query=redux',
   );
   setData(result.data);
 });
 return (
   ...
 );
}
// good
function App() {
 const [data, setData] = useState({ hits: [] });
 useEffect(async () => {
   const result = await axios(
     'https://hn.algolia.com/api/v1/search?query=redux',
   );
   setData(result.data);
 }, []);
 return (
   ...
 ); 
}

完整的列出useEffect的依赖,不要欺骗react

// bad
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(id);

 }, []);

  return <h1>{count}</h1>;
}
// good
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);
    return () => clearInterval(id);

 }, [count]);

  return <h1>{count}</h1>;
}

typescript规范

有jsx代码的ts文件后缀名使用.tsx,没有的用ts

类型名使用PascalCase,枚举值使用PascalCase,枚举成员大写,接口前不要加I

在每个文件中,类型定义应该放在最前面

尽量减少使用any作为类型,类型应尽量详细(建议)

interface声明顺序 只读参数放第一位,必选参数第二位,可选参数次之,不确定参数放最后

interface Props {
  readonly x: number;
  readonly y: number;
  name: string;
  age: number; 
  height?: number;
  [propName: string]: any;
}

使用ts内置工具泛型(建议)Record

type Record<K extends keyof any, T> = {
   [P in K]: T;
};

可用来声明对象结构的类型。

type Foo = Record<'a' | ‘b’, string>

const foo: Foo = {a: '1'} // 正确
const foo: Foo = {b: '1'} // 错误,因为 key 不为 a
const foo: Foo = {a: 1} // 错误,因为 value 的值不是 string 类型

Partial
type Partial<T> = {
   [P in keyof T]?: T[P];
};

将传入的接口的必选属性变为可选属性。

interface iPeople {
    title: string;
    name: string;
}

const people: Partial<iPeople> = {
    title: 'Delete inactive users',
};

Required

type Required<T> = {
   [P in keyof T]-?: T[P];
};

将传入的接口的可选属性变为必选属性

interface iPeople {
 title?: string;
 name?: string;
}

const people: Required<iPeople> = { title: 'ts' }; // Error: property 'name' missing

Readonly

type Readonly<T> = {
   readonly [P in keyof T]: T[P];
};

将传入的接口的属性变为只读属性

interface iPeople {
    title: string;
    name: string;
}

const people: Readonly<iPeople> = {
    title: 'todo list';
    name: 'ts';
};
// title name属性就是只读的了

Pick

type Pick<T, K extends keyof T> = {
   [P in K]: T[P];
};

从传入的接口的中提取一部分属性作为新的接口。

interface iPeople {
 name: string;
 age: number;
 gender: number;
}

const pickPerson: Pick<iPeople, 'name' | 'age'> = {name: 'test', age: 22};

Exclude

type Exclude<T, U> = T extends U ? never : T;

排除掉T中可以赋值给U的类型。

type ExcludeType = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">;
// ExcludeType = 'b' | 'd'

Extract

type Extract<T, U> = T extends U ? T : never;

提取出T中可以赋值给U的类型。

type ExtractType = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">;
// ExcludeType = 'a' | 'c'

Omit

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

基于T生成新的type,其中排除掉了在K中的属性。

interface iPeople {
 name: string;
 age: number;
 gender: number;
}
type OmitPeople = Omit<iPeople, 'name'>;
// OmitPeople = { age: number, gender: number}

减少“魔数”

写代码的时候尽量减少一些未知含义的数字或者布尔值,尽量用英文单词。

// bad
if (type !== 0) {
  // TODO
}

// good
const STATUS: Record<string, any> = {
  READY: 0,
  FETCHING: 1,
  FAILED: 2
};

if (type === STATUS.READY) {
  // TODO
}

// best
enum STATUS {
  // 就绪
  READY = 0,
  // 请求中
  FETCHING = 1,
  // 请求失败
  FAILED = 2,
}

使用as作为类型断言,不要使用<>

interface Foo {
  bar: number;
  bas: string;
}
// bad
const foo = <Foo>{};
foo.bar = 123;
foo.bas = 'hello';
// good
const foo = {} as Foo;
foo.bar = 123;
foo.bas = 'hello';

S

推荐阅读