ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

React/JSX 编码规范

2026/9/5 3:25:36 拓冰建站 浏览量
React/JSX 编码规范 目录基本规范创建模块命名声明模块代码对齐单引号还是双引号空格属性引用括号标签函数模块生命周期A11Y 无障碍规范Hook基本规范每个文件只写一个模块多个无状态函数组件可以放在单个文件中。eslint:react/no-multi-comp推荐使用 JSX 语法。不要使用React.createElement除非从非 JSX 文件初始化应用。创建模块Class /createClass/ 无状态组件选择组件存在内部 state 或者 ref推荐使用class extends React.Component不使用React.createClasseslint:react/prefer-es6-class、react/prefer-stateless-function// bad const Listing React.createClass({ render() { return div{this.state.hello}/div; } }); // good class Listing extends React.Component { render() { return div{this.state.hello}/div; } }组件无状态、无 ref推荐使用普通 function 函数不要用 class不推荐依赖名称推断的箭头函数// bad class Listing extends React.Component { render() { return div{this.props.hello}/div; } } // bad (不推荐依赖函数名推断) const Listing ({ hello }) ( div{hello}/div ); // good function Listing({ hello }) { return div{hello}/div; }命名eslint:react/jsx-pascal-case文件名帕斯卡命名例ReservationCard.jsx组件引用React 组件名帕斯卡命名组件实例使用小驼峰命名// bad import reservationCard from ./ReservationCard; // good import ReservationCard from ./ReservationCard; // bad const ReservationItem ReservationCard /; // good const reservationItem ReservationCard /;模块命名组件名和文件名保持一致文件夹作为组件入口时使用index.js直接引入文件夹名// bad import Footer from ./Footer/Footer; // bad import Footer from ./Footer/index; // good import Footer from ./Footer;高阶组件 HOC生成组件的displayName拼接高阶组件名 被包裹组件名方便调试与开发者工具查看// bad export default function withFoo(WrappedComponent) { return function WithFoo(props) { return WrappedComponent {...props} foo /; } } // good export default function withFoo(WrappedComponent) { function WithFoo(props) { return WrappedComponent {...props} foo /; } const wrappedComponentName WrappedComponent.displayName || WrappedComponent.name || Component; WithFoo.displayName withFoo(${wrappedComponentName}); return WithFoo; }属性命名不要复用 DOM 原生属性名用于自定义含义不要用style/className做业务标记// bad MyComponent stylefancy / // good MyComponent variantfancy /声明模块不要手动写displayName直接使用 class / 函数名称作为组件名称// bad export default React.createClass({ displayName: ReservationCard, }); // good export default class ReservationCard extends React.Component { }禁止使用require引入组件 / 图片统一使用 ES6import语法// bad const qrCodeImg require(/assets/img/enter.png); img src{qrCodeImg} alt / // good import qrCodeImg from /assets/img/enter.png; img src{qrCodeImg} alt /代码对齐eslint:react/jsx-closing-bracket-location、react/jsx-closing-tag-location多行属性每个属性单独一行闭合标签另起一行单行可容纳全部属性直接写一行子元素正常缩进// bad Foo superLongParambar anotherSuperLongParambaz / // good多行属性闭合标签另起一行 Foo superLongParambar anotherSuperLongParambaz / // 单行容纳直接一行 Foo barbar / // 子元素缩进 Foo superLongParambar anotherSuperLongParambaz Quux / /Foo单引号还是双引号eslint:jsx-quotesJSX 属性值使用双引号JS 普通字符串使用单引号对齐 HTML 习惯。// bad Foo barbar / // good Foo barbar /空格自闭合标签/前面保留一个空格eslint:react/jsx-tag-spacing// bad Foo/ // very bad Foo / // bad Foo / // good Foo /JSX{}表达式内部不要加多余空格eslint:react/jsx-curly-spacing// bad Foo bar{ baz } / // good Foo bar{baz} /JSX 属性等号两侧禁止空格eslint:react/jsx-equals-spacing// bad Hello name {firstname} /; Hello name {firstname} /; Hello name {firstname} /; // good Hello name{firstname} /;属性JSX 属性名使用小驼峰camelCase// bad Foo UserNamehello phone_number{12345678} / // good Foo userNamehello phoneNumber{12345678} /属性值为true时直接省略值eslint:react/jsx-boolean-value// bad Foo hidden{true} / // good Foo hidden /禁止使用未知 DOM 属性class 写成classNameeslint:react/no-unknown-property// bad div classhelloHello World/div // good div classNamehelloHello World/divimg标签必须写alt纯装饰图片可以alt或增加rolepresentationeslint:jsx-a11y/alt-text// bad img srchello.jpg / // good img srchello.jpg altMe waving hello / // good img srchello.jpg alt / // good img srchello.jpg rolepresentation /alt 文本不要包含 image /photo/picture 这类图片描述词eslint:jsx-a11y/img-redundant-alt// bad img srchello.jpg altPicture of me waving hello / // good img srchello.jpg altMe waving hello /使用合法有效的 ARIA role禁止抽象 roleeslint:jsx-a11y/aria-role// bad - 非法role div roledatepicker / // bad - 抽象role div rolerange / // good div rolebutton /禁止使用accessKey属性eslint:jsx-a11y/no-access-key// bad div accessKeyh / // good div /数组渲染列表key不要使用数组 index优先使用业务唯一 id// bad {todos.map((todo, index) Todo {...todo} key{index} / )} // good {todos.map(todo ( Todo {...todo} key{todo.id} / ))}非必传属性显式定义defaultProps作为组件文档// bad function SFC({ foo, bar, children }) { return div{foo}{bar}{children}/div; } SFC.propTypes { foo: PropTypes.number.isRequired, bar: PropTypes.string, children: PropTypes.node, }; // good function SFC({ foo, bar, children }) { return div{foo}{bar}{children}/div; } SFC.propTypes { foo: PropTypes.number.isRequired, bar: PropTypes.string, children: PropTypes.node, }; SFC.defaultProps { bar: , children: null, };谨慎使用扩展运算符{...props}尽量解构剔除不需要的属性高阶组件为特例允许透传// good 剔除无关属性再透传 render() { const { irrelevantProp, ...relevantProps } this.props; return WrappedComponent {...relevantProps} / }JSX 单行最多 1 个 prop超过 1 个就换行分行书写eslint:react/jsx-max-props-per-line// bad Hello lastNameSmith firstNameJohn /; // good Hello firstNameJohn lastNameSmith /;JSX 禁止重复属性eslint:react/jsx-no-duplicate-props// bad Hello nameJohn nameJohn /; // good Hello firstNameJohn lastNameSmith /;a 标签target_blank必须增加relnoopener noreferrer防止安全漏洞eslint:react/jsx-no-target-blank// bad a target_blank hrefhttp://example.com/a // good a target_blank relnoopener noreferrer hrefhttp://example.com/a引用ref 优先使用回调 ref禁止字符串 refeslint:react/no-string-refs// bad Foo refmyRef / // good Foo ref{(ref) { this.myRef ref; }} /括号多行 JSX 返回必须包裹在()内单行 JSX 不需要eslint:react/jsx-wrap-multilines// bad render() { return MyComponent classNamelong body foobar MyChild / /MyComponent; } // good render() { return ( MyComponent classNamelong body foobar MyChild / /MyComponent ); } // good单行无需括号 render() { const body divhello/div; return MyComponent{body}/MyComponent; }标签无子女的标签必须自闭合eslint:react/self-closing-comp// bad Foo classNamestuff/Foo // good Foo classNamestuff /多行属性时自闭合标签/单独起一行eslint:react/jsx-closing-bracket-location// bad Foo barbar bazbaz / // good Foo barbar bazbaz /函数列表渲染事件回调使用箭头函数捕获局部变量function ItemList(props) { return ( ul {props.items.map((item, index) ( Item key{item.key} onClick{() doSomethingWith(item.name, index)} / ))} /ul ); }class 组件事件处理函数推荐类属性箭头函数避免 render 里 bind减少每次渲染新建函数开销eslint:react/jsx-no-bind// bad class extends React.Component { onClickDiv() { // do stuff } render() { return div onClick{this.onClickDiv.bind(this)} /; } } // good 构造函数预绑定 class extends React.Component { constructor(props) { super(props); this.onClickDiv this.onClickDiv.bind(this); } onClickDiv() { // do stuff } render() { return div onClick{this.onClickDiv} /; } } // very good 类属性箭头函数推荐 class extends React.Component { onClickDiv () { // do stuff } render() { return div onClick{this.onClickDiv} / } }render必须显式return返回值eslint:react/require-render-return// bad render() { (div /); } // good render() { return (div /); }禁止在componentDidUpdate中调用setStateeslint:react/no-did-update-set-state// bad class Hello extends React.Component { componentDidUpdate() { this.setState({ name: this.props.name.toUpperCase() }); } render() { return divHello {this.state.name}/div; } }; // good class Hello extends React.Component { componentDidUpdate() { this.props.onUpdate(); } render() { return divHello {this.props.name}/div; } };禁止在componentWillUpdate调用setStateeslint:react/no-will-update-set-state不要使用ReactDOM.render返回的实例eslint:react/no-render-return-value// bad const inst ReactDOM.render(App /, document.body); doSomethingWithInst(inst); // good ReactDOM.render(App ref{doSomethingWithInst} /, document.body); ReactDOM.render(App /, document.body, doSomethingWithInst);模块生命周期React 16.3 生命周期规范新增static getDerivedStateFromProps、getSnapshotBeforeUpdate标记 UNSAFE禁止使用UNSAFE_componentWillMount/UNSAFE_componentWillUpdate/UNSAFE_componentWillReceivePropsclass 组件生命周期顺序constructor构造函数static getDerivedStateFromProps组件接收新数据render()componentDidMount首次渲染完成shouldComponentUpdate判断是否重渲染getSnapshotBeforeUpdatecomponentDidUpdate更新渲染结束componentWillUnmount组件销毁清理资源方法命名约定事件回调onClickSubmit()/onChangeDescription()render 内 gettergetSelectReason()/getFooterContent()分段渲染函数renderNavigation()/renderProfilePicture()propTypes /defaultProps 示例import React from react; import PropTypes from prop-types; const propTypes { id: PropTypes.number.isRequired, url: PropTypes.string.isRequired, text: PropTypes.string, }; const defaultProps { text: Hello World, }; class Link extends React.Component { static methodsAreOk() { return true; } render() { return a href{this.props.url} data-id{this.props.id}{this.props.text}/a; } } Link.propTypes propTypes; Link.defaultProps defaultProps; export default Link;A11Y 无障碍规范全部规则来自eslint-plugin-jsx-a11yARIA role 必须合法、非抽象自定义组件不检测// good div rolebutton/div div role{role}/div div/div Foo/Fooaria-* 属性名称必须合法// bad input aria-labeledbyaddress_label // good input aria-labelledbyaddress_labelARIA 属性值类型合法布尔属性只能 true/false// Bad span aria-hiddenyesfoo/span // Good span aria-hiddentruefoo/spanimg/area/input [typeimage]/object 必须提供替代文本// Bad img srcfoo / // Good img srcfoo altFoo eating a sandwich. / object aria-labelfoo /img alt 禁止冗余描述词 (image/photo/picture)label 必须关联表单控件两种方式内部包裹控件 /htmlFor id 绑定// bad labelSurname/label input typetext / // good label Surname input typetext / /label label htmlFor{domId}Surname/label input typetext id{domId} /onMouseOver/onMouseOut必须配套onFocus/onBlur支持键盘访问// good div onMouseOver{()void 0} onFocus{()void 0} / div onMouseOut{()void 0} onBlur{()void 0} /交互式元素带 onClick、role 交互角色必须可聚焦// bad span onClick{submitForm} rolebuttonSubmit/span // good span onClick{doSomething} tabIndex0 rolebuttonClick me!/span button onClick{doSomething}Click the button :)/buttonrole 如果有必填 aria 属性必须补齐// bad span rolecheckbox aria-labelledbyfoo tabindex0/span // good span rolecheckbox aria-checkedfalse aria-labelledbyfoo tabindex0/span不要使用大于 0 的 tabIndex不要手动干预 tab 顺序// Bad span tabIndex5foo/span // Good span tabIndex0foo/span span tabIndex-1bar/span标题 h1~h6 必须包含可读内容不能整体 aria-hidden// Bad h1 / // Good h1Heading Content!/h1html 标签必须设置 lang 属性// Bad html // Good html langzh-CN禁止使用marquee、blink干扰阅读元素scope属性只能放在th标签// bad div scope / // good th scopecol /Emoji 表情增加roleimg与aria-label支持读屏// good span roleimg aria-labelPanda/spaniframe 必须有非空 title// Bad iframe / // Good iframe title页面内嵌表单 /audio/video 媒体标签需要字幕 trackmuted 视频例外// good video track kindcaptions / /video video muted/videoHookeslint:react-hooks/rules-of-hooks只能在顶层调用 Hook不能放在循环、if 条件、嵌套函数内部保证每次渲染 Hook 调用顺序完全一致。只能在 React 函数组件 / 自定义 Hook 中调用 Hook普通 JS 函数不能调用 Hook。✅ 如果需要条件逻辑条件写在 useEffect 内部不要包裹 useEffect// 正确示例 function Form() { const [name, setName] useState(Mary); useEffect(function persistForm() { // 判断写在effect里面 if (name ! ) { localStorage.setItem(formData, name); } }); const [surname, setSurname] useState(Poppins); useEffect(function updateTitle() { document.title name surname; }); }原理React 依靠 Hook 调用顺序绑定 state顺序改变会造成状态错乱。