在每个 HTML 页面的开头使用这个简单的文档类型,在每个浏览器中强制执行标准模式和更一致的呈现。
从 HTML5 规范:
鼓励作者在根 html 元素上指定 lang 属性,给出文档的语言。这有助于语音合成工具确定使用什么发音,帮助翻译工具确定使用什么规则,等等。
<!doctype html>
<html lang="zh-CN">
<head>
</head>
</html>
通过声明明确的字符编码,快速轻松地确保正确呈现您的内容。这样做时,可以避免在 HTML 中使用字符实体,只要它们的编码与文档的编码匹配(通常是 UTF-8)。
type
在包含 CSS 和 JavaScript 文件时指定 text/css
和text/javascript
是它们各自的默认值。布尔属性是不需要声明值的属性。声明其属性名即为true,不写即为false
<select>
<option value="1" selected>1</option>
</select>
<video control autoplay></video>
尽可能在编写 HTML 时避免多余的父元素。举个例子:
<!-- Bad -->
<span class="avatar">
<img src="...">
</span>
<!-- Good -->
<img class="avatar" src="...">
box-shadow
)。rgb()
, rgba()
, hsl()
, hsla()
, 或rect()
值中包含逗号后的空格。这有助于将多个颜色值(逗号,无空格)与多个属性值(逗号和空格)区分开来。.5
代替0.5
和-.5px
代替-0.5px
)。#fff
. 查看时,小写字母更容易辨别。#fff
而不是#ffffff
。margin: 0;
而不是margin: 0px;
。定位是第一位的,因为它可以从正常的文档流中移除一个元素并覆盖与盒子模型相关的样式。盒子模型无论是 flex、float、grid 还是 table都遵循它规定了组件的尺寸、放置和对齐方式。其他一切都发生在组件内部或不影响前两个部分,因此它们排在最后。
.example {
/* 定位属性 */
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 100;
/* 盒子模型 */
display: flex;
align-items: center;
width: 100px;
height: 100px;
/* 视觉展示 */
background-color: #f5f5f5;
border: 1px solid #e5e5e5;
border-radius: 3px;
/* 文字排版 */
font: normal 13px "Helvetica Neue", sans-serif;
line-height: 1.5;
color: #333;
text-align: center;
/* 其他项 */
opacity: 1;
cursor: pointer;
}
尽可能将媒体查询放置在其相关规则集附近。不要将它们全部捆绑在单独的样式表中或文档末尾。
.element { ... }
.element-avatar { ... }
.element-selected { ... }
@media (min-width: 480px) {
.element { ...}
.element-avatar { ... }
.element-selected { ... }
}
避免不必要的嵌套。仅仅因为可以嵌套,并不意味着就总是应该。仅当必须将样式范围限定为父级并且要嵌套多个元素时才考虑嵌套。
导入顺序为 node_modules
-> $src/开头文件
-> 相对路径文件
-> 当前组件样式文件
// 导入 node_modules 依赖
import React from 'react';
// 导入公共组件
import Button from '$src/components/Button';
// 导入相对路径组件
import Foo from './Foo';
// 导入对应同名的 .less 文件,命名为 styles
import styles from './style.module.scss';
我们以组件的颗粒度大小为抽象单元,样式文件则应与组件本身保持一致。不推荐交叉引入样式文件的做法,这样会导致重构混乱,无法明确当前这个样式被多少个组件使用。
- Foo.jsx
- Foo.module.scss
- Bar.jsx
- Bar.module.scss
组件内部的顺序为 state
-> custom Hooks
-> effects
-> 内部 function
-> 其他逻辑
-> JSX
/**
* 组件注释(简明概要)
*/
const Foo: React.FC<FooProps> = ({ bar }) => {
// 1. state
// 2. custom Hooks
// 3. effects
// 4. 内部 function
// 5. 其他逻辑...
return (
<div className={styles.wrapper}>
{bar}
<Child />
</div>
);
};
内部方法按照 handle{Type}{Event}
命名,例如 handleNameChange
。暴露外部的方法按照 on{Type}{Event}
,例如 onNameChange
。这样做的好处可以直接通过函数名区分是否为外部参数。
例如 antd/Button 组件片段:
const handleClick = (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement, MouseEvent>) => {
const { onClick, disabled } = props;
if (innerLoading || disabled) {
e.preventDefault();
return;
}
(onClick as React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>)?.(e);
};
export default
导出未命名的组件这种方式导出的组件在 React Inspector
查看时会显示为 Unknown
// 错误做法
export default () => {};
// 正确做法
export default function Foo() {}
// 推荐做法
function Foo() {}
export default Foo;
在 React
表单管理中有两个经常使用的术语: 受控输入和非受控输入。简单来说,受控的意思是当前组件的状态成为该表单的唯一数据源。表明这个表单的值在当前组件的控制中,并只能通过 setState
来更新。
受控/非受控的概念在组件设计上极为常见。受控组件通常以 value
与 onChange
成对出现。传入到子组件中,子组件无法直接修改这个 value
,只能通过 onChange
回调告诉父组件更新。 非受控组件则可以传入 defaultValue
属性来提供初始值。
若该状态作为组件的核心逻辑时,那么它应该支持受控,或兼容非受控模式。若该状态为次要逻辑,可以根据实际情况选择性支持受控模式。
例如 Select
组件处理受控与非受控逻辑:
function Select(props) {
// value 和 onChange 为核心逻辑,支持受控。兼容传入 defaultValue 成为非受控
// defaultOpen 为次要逻辑,可以非受控
const { value: controlledValue, onChange: onControlledChange, defaultValue, defaultOpen } = props;
// 非受控模式使用内部 state
const [innerValue, onInnerValueChange] = React.useState(defaultValue);
// 次要逻辑,选择框展开状态
const [visible, setVisible] = React.useState(defaultOpen);
// 通过检测参数上是否包含 value 的属性判断是否为受控,尽管 value 为 undefined
const shouldControlled = Reflect.has(props, 'value');
// 支持受控和非受控处理
const value = shouldControlled ? controlledValue : innerValue;
const onChange = shouldControlled ? onControlledChange : onInnerValueChange;
// ...
}
通用性设计其实是一定意义上放弃对 DOM 的掌控,而将 DOM 结构的决定权转移给开发者,比如预留自定义渲染。
举个例子, fusion
中的 Table
通过 cell
函数将每个单元格渲染的决定权交给使用者,这样极大提高了组件的可扩展性:
const columns = [
{
title: '名称',
dataIndex: 'name',
width: 200,
cell(value) {
return <em>{value}</em>;
},
},
];
<Table columns={columns} />;
当各个组件数量变多之后,组件与组件直接可能存在某种契合的关系,我们可以统一某种行为 API 的一致性,这样可以降低使用者对各个组件 API 名称的心智负担。否则组件传参就会如同一根一根数面条一样痛苦。
举个例子,经典的 value
与 onChange
的 API 可以在各个不同的表单域上出现。可以通过包装的方式导出更多高阶组件,这些高阶组件又可以被表单管理组件所容纳。
我们可以约定在各个组件上比如 visible
、onVisibleChange
、bordered
、size
、onSubmit
这样的 API,使其在各个组件中保持一致性。
组件名 + Props
形式命名 Props
类型并导出。类型与参数书写的顺序保持一致。变量的注释禁止放末尾,原因是会导致编辑器识别错位,无法正确提示
/**
* 类型定义(命名:组件名 + Props)
*/
export interface FooProps {
/**
* 多行注释(建议)
*/
email: string;
// 单行注释(不推荐)
mobile: string;
username: string; // 末尾注释(禁止)
}
React.FC
定义const Foo: React.FC<FooProps> = ({ email, mobile, usename }) => {};
以下例子,可以用过泛型让 value
和 onChange
回调中的类型保持一致,并做到编辑器智能类型提示。
export interface FooProps<T> {
value: T;
onChange: (value: T) => void;
}
export function Foo<T extends React.Key>(props: FooProps<T>) {}
any
类型无论隐式和显式的方式,都不推荐使用 any
类型。定义了 any
的参数会让使用该组件的人产生极度困惑,无法明确地知道其中的类型。我们可以通过泛型的方式去声明。
// 隐式 any (禁止)
let foo;
function bar(param) {}
// 显式 any (禁止)
let hello: any;
function world(param: any) {}
// 使用泛型继承,缩小类型范围 (推荐)
function Foo<P extends Record<string, unknow>>(param: P) {}
Copyright © Chenyz的知识星球🌍 2025.豫ICP备2024045759号