-
1.1.1【强制】JSX 语法使用 2 个空格缩进。eslint: react/jsx-indent react/jsx-indent-props react/jsx-closing-tag-location
对于 JSX 语法,遵循与 JS 规约和 HTML 规约一致的 2 个空格缩进,不要使用 4 空格或 tab 缩进:
// bad <Foo superLongParam="bar" anotherSuperLongParam="baz" > <Quux /> </Foo> // good <Foo superLongParam="bar" anotherSuperLongParam="baz" > <Quux /> </Foo>
-
1.2.1【强制】自闭合标签的斜线前有且仅有一个空格。eslint: no-multi-spaces react/jsx-tag-spacing
// bad <Foo/> // very bad <Foo /> // bad <Foo /> // good <Foo />
-
1.2.2【强制】JSX 行内属性之间仅有一个空格。eslint: react/jsx-props-no-multi-spaces
同一行中标签和属性之间、属性之间只有一个空格。
// bad <App spacy /> <App too spacy /> // good <App cozy /> <App very cozy />
-
1.2.3【强制】JSX 属性的大括号内部两侧无空格。eslint: react/jsx-curly-spacing
// bad <Foo bar={ baz } /> // good <Foo bar={baz} />
-
1.2.4【强制】不要在 JSX 属性的等号两边加空格。eslint: jsx-equals-spacing
// bad <Hello name = {firstname} />; // good <Hello name={firstname} />;
-
1.3.1【强制】JSX 属性使用双引号,不要使用单引号。eslint: jsx-quotes
为什么?HTML 属性通常使用双引号而不是单引号,因此 JSX 属性沿用了这种约定。 其他 JS 使用单引号。
// bad <Foo bar='bar' /> // good <Foo bar="bar" /> // bad <Foo style={{ left: "20px" }} /> // good <Foo style={{ left: '20px' }} />
-
1.4.1【强制】多行的 JSX 标签需用小括号包裹。eslint: react/jsx-wrap-multilines
// bad render() { return <MyComponent variant="long body" foo="bar"> <MyChild /> </MyComponent>; } // good render() { return ( <MyComponent variant="long body" foo="bar"> <MyChild /> </MyComponent> ); } // good - 单行的 jsx 无需加圆括号 render() { const body = <div>hello</div>; return <MyComponent>{body}</MyComponent>; }
-
1.5.1【强制】无子元素的标签需写成自闭合标签。eslint: react/self-closing-comp
// bad <Foo variant="stuff"></Foo> // good <Foo variant="stuff" />
-
1.5.2【强制】标签属性的换行。eslint: react/jsx-max-props-per-line react/jsx-first-prop-new-line
对 JSX 标签属性的换行,遵循以下规则:
- 标签名和它的属性可以写在一行,前提是不超过单行最大 100 字符数的限制
- 如果标签有多个属性,且存在换行,则每个属性都需要换行独占一行
// bad - 属性应全部换行,或全部跟组件名写在一行 <Foo superLongParam="bar" anotherSuperLongParam="baz" /> // good <Foo superLongParam="bar" anotherSuperLongParam="baz" /> // good - 组件名和属性可以写在一行,前提是不超过单行最大字符限制 <Foo bar="bar" /> // bad <Hello foo={{ }} bar /> // good <Hello foo={{ }} /> <Hello foo={{ }} bar />
-
1.5.3【强制】标签的属性有多行时,结束标签需另起一行。eslint: react/jsx-closing-bracket-location
// bad <Foo bar="bar" baz="baz" /> // good <Foo bar="bar" baz="baz" />
-
1.5.4【强制】禁止在有子节点的组件或 DOM 元素中使用 dangerouslySetInnerHTML 属性。eslint: react/no-danger-with-children
// bad <div dangerouslySetInnerHTML={{ __html: "HTML" }}> Children </div> <Hello dangerouslySetInnerHTML={{ __html: "HTML" }}> Children </Hello> // good <div dangerouslySetInnerHTML={{ __html: "HTML" }} /> <Hello dangerouslySetInnerHTML={{ __html: "HTML" }} /> <div> Children </div> <Hello> Children </Hello>
-
1.5.5【强制】HTML 自闭标签不能有子节点。eslint: react/void-dom-elements-no-children
HTML 自闭标签,比如 img,br,hr,被统称为空 DOM 元素,不能给他们定义子节点。
// bad <br>Children</br> <br dangerouslySetInnerHTML={{ __html: 'HTML' }} /> // good <div>Children</div> <div dangerouslySetInnerHTML={{ __html: 'HTML' }} />
-
1.5.6【推荐】不要使用危险属性。eslint: react/no-danger
React中的危险属性是指那些已知会引起应用程序漏洞的属性。这些属性命名为
dangerouslyXyz
已经清楚地表明它们是危险的,应该尽量避免使用。详细文档// bad <div dangerouslySetInnerHTML={{ __html: "Hello World" }}></div>; // good <div>Hello World</div>;
-
1.5.7【强制】JSX 语句的文本节点中不要使用注释字符串(例如,以//或/ *开头)。eslint: react/jsx-no-comment-textnodes
// bad class Hello extends React.Component { render() { return ( <div>// empty div</div> ); } }; class Hello extends React.Component { render() { return ( <div> /* empty div */ </div> ); } }; // good class Hello extends React.Component { render() { return <div>{/* empty div */}</div>; } }; class Hello extends React.Component { render() { return <div /* empty div */></div>; } }; class Hello extends React.Component { render() { return <div className={'foo' /* temp class */}</div>; } };
-
1.5.8【强制】标签中禁止出现无意义字符,比如 > " } '。eslint: react/no-unescaped-entities
>
可用>
替代"
可用"
,“
,"
或者”
替代'
可用'
,‘
,'
或者’
替代}
可用}
替代或者写在表达式里,比如
<div>{'>'}</div>
// bad <MyComponent a="b"> {/* oops! */} c="d" Intended body text </MyComponent> // good <div> > </div> <div> {'>'} </div>
-
2.1.1【参考】使用
JSX
语法时,防止React
变量被标记为未使用,可以使用@jsx
标注来指定React
之外的变量。eslint: react/jsx-uses-react// bad var React = require('react'); // nothing to do with React /** @jsx Foo */ var React = require('react'); var Hello = <div>Hello {this.props.name}</div>; // good var React = require('react'); var Hello = <div>Hello {this.props.name}</div>; /** @jsx Foo */ var Foo = require('foo'); var Hello = <div>Hello {this.props.name}</div>;
-
2.1.2【强制】不要使用未声明的组件。eslint: react/jsx-no-undef react/jsx-uses-vars
不允许没有引用组件就直接使用,也可能是组件名拼写错误。
// bad <Hello name="John" />; // good import Hello from './Hello'; <Hello name="John" />;
-
2.1.3【强制】每个文件只包含一个 React 组件。eslint: react/no-multi-comp
但是可以包含多个函数组件。
-
2.1.4【强制】不要在函数组件中使用 this。eslint: react/no-this-in-sfc
// bad function Foo(props, context) { return ( <div> {this.context.foo ? this.props.bar : ''} </div> ); } // good function Foo(props, context) { return ( <div> {context.foo ? props.bar : ''} </div> ); }
-
2.1.5【强制】使用 ES6 class 创建组件 ,而不是 createReactClass 。eslint: react/prefer-es6-class
// bad const Listing = createReactClass({ // ... render() { return <div>{this.state.hello}</div>; } }); // good class Listing extends React.Component { // ... render() { return <div>{this.state.hello}</div>; } }
-
2.1.6【参考】如果组件没有内部状态或 refs ,应使用函数组件,而不是类组件。eslint: react/prefer-stateless-function
// 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>; }
-
2.1.7【强制】不要使用 React.createElement,除非你不是用 JSX 文件初始化应用程序。
-
2.2.1【推荐】不要在 JSX 属性中使用 .bind()。eslint: react/jsx-no-bind
这不利于组件性能,每次 render 都会创建一个新的函数。
有 2 种替代方案:
- 在
constructor
中绑定事件处理函数 - 使用 react 的 property initializers 特性或 ES7 autobind decorator
// bad class extends React.Component { onClickDiv() { // ... } render() { return <div onClick={this.onClickDiv.bind(this)} />; } } // good - 在 constructor 中绑定事件处理函数 class extends React.Component { constructor(props) { super(props); this.onClickDiv = this.onClickDiv.bind(this); } onClickDiv() { // ... } render() { return <div onClick={this.onClickDiv} />; } } // good - 使用 react 的 property initializers 特性 class extends React.Component { constructor(props) { super(props); } onClickDiv = () => { // ... } render() { return <div onClick={this.onClickDiv} />; } }
- 在
-
2.2.2【强制】render 方法必须要有返回值。eslint: react/require-render-return
// bad render() { (<div />); } // good render() { return (<div />); }
-
2.2.3【强制】禁止使用 ReactDOM.render 的返回值。eslint: react/no-render-return-value
render()返回 ReactComponent 实例的引用。然而,应该避免使用这个返回值,因为在某些情况下,React 的未来版本中 render 方法可能会异步执行。如果需要引用 ReactComponent 实例,根元素需要增加 ref 回调。
// bad const inst = ReactDOM.render(<App />, document.body); doSomethingWithInst(inst); // good ReactDOM.render(<App ref={doSomethingWithInst} />, document.body); ReactDOM.render(<App />, document.body, doSomethingWithInst);
-
2.2.4【强制】在扩展 React.PureComponent 时禁止使用 shouldComponentUpdate。eslint: react/no-redundant-should-component-update
定义 React.PureComponent 扩展组件时使用 shouldComponentUpdate 虽然有效,但是扩展 PureComponent 变得毫无意义。
// bad class Foo extends React.PureComponent { shouldComponentUpdate() { // do check } render() { return <div>Radical!</div> } } function Bar() { return class Baz extends React.PureComponent { shouldComponentUpdate() { // do check } render() { return <div>Groovy!</div> } } } // good class Foo extends React.Component { shouldComponentUpdate() { // do check } render() { return <div>Radical!</div> } } function Bar() { return class Baz extends React.Component { shouldComponentUpdate() { // do check } render() { return <div>Groovy!</div> } } } class Qux extends React.PureComponent { render() { return <div>Tubular!</div> } }
-
2.2.5【强制】禁止使用已经废弃的方法。eslint: react/no-deprecated
随着React版本升级,有些方法逐渐被弃用。
// bad React.render(<MyComponent />, root); React.unmountComponentAtNode(root); React.findDOMNode(this.refs.foo); React.renderToString(<MyComponent />); React.renderToStaticMarkup(<MyComponent />); React.createClass({ /* Class object */ }); const propTypes = { foo: PropTypes.bar, }; //Any factories under React.DOM React.DOM.div(); import React, { PropTypes } from 'react'; class Foo extends React.Component { componentWillMount() { } componentWillReceiveProps() { } componentWillUpdate() { } // ... } class Foo extends React.PureComponent { componentWillMount() { } componentWillReceiveProps() { } componentWillUpdate() { } // ... } var Foo = createReactClass({ componentWillMount: function() {}, componentWillReceiveProps: function() {}, componentWillUpdate: function() {}, // ... }) // good ReactDOM.render(<MyComponent />, root); // When [1, {"react": "0.13.0"}] ReactDOM.findDOMNode(this.refs.foo); import { PropTypes } from 'prop-types'; class Foo { componentWillMount() { } componentWillReceiveProps() { } componentWillUpdate() { } }
-
2.2.6【强制】不要使用 findDOMNode。eslint: react/no-find-dom-node
// bad class MyComponent extends Component { componentDidMount() { findDOMNode(this).scrollIntoView(); } render() { return <div /> } } // good class MyComponent extends Component { componentDidMount() { this.node.scrollIntoView(); } render() { return <div ref={node => this.node = node} /> } }
-
2.2.7【强制】不要使用 componentWillMount、componentWillReceiveProps、componentWillUpdate。
不要再使用 componentWillMount 、componentWillReceiveProps、componentWillUpdate。使用这些生命周期方法通常会导致错误和不一致,因此React 计划在17版本删掉这些方法。
- componentWillMount() 可以用 constructor() 或 componentDidMount() 替代;
- componentWillReceiveProps() 可以用 componentDidUpdate() 或其他方式替换;
- componentWillUpdate() 可以用 componentDidUpdate() 替换或者把逻辑写在 getSnapshotBeforeUpdate() 中。
使用rename-unsafe-lifecycles codemod自动为不推荐使用的生命周期钩子添加“UNSAFE_”前缀。转化为
- UNSAFE_componentWillMount()
- UNSAFE_componentWillReceiveProps()
- UNSAFE_componentWillUpdate()
-
2.2.8【强制】不要在 componentWillUpdate 内改变 state 值。eslint: react/no-will-update-set-state
首先,不要再使用 componentWillUpdate,React 未来在17版本计划删掉 componentWillUpdate。通常可以用 componentDidUpdate() 替代。使用rename-unsafe-lifecycles codemod自动更新组件。
不要在 componentWillUpdate 调用 this.setState()。若你需要更新状态响应属性的变更,使用 getDerivedStateFromProps() 代替。在 componentWillUpdate 中改变 state 的值可能会引起组件的不确定状态。
// bad class Hello extends React.Component { componentWillUpdate() { this.setState({ name: this.props.name.toUpperCase() }); } render() { return <div>Hello {this.state.name}</div>; } }; // good class Hello extends React.Component { componentWillUpdate() { this.props.prepareHandler(); } render() { return <div>Hello {this.props.name}</div>; } }; class Hello extends React.Component { componentWillUpdate() { this.prepareHandler(function callback(newName) { this.setState({ name: newName }); }); } render() { return <div>Hello {this.props.name}</div>; } };
-
2.3.1【强制】采用小驼峰风格命名 prop 。eslint: react/no-unknown-property
// bad <Foo UserName="hello" phone_number={12345678} /> // good <Foo userName="hello" phoneNumber={12345678} />
-
2.3.2【强制】声明的 prop 必须被使用。eslint: react/no-unused-prop-types
声明而未使用的 prop 可能带来潜在的问题,也会给维护者造成困扰,应将它们删除。
// bad var Hello = createReactClass({ propTypes: { name: PropTypes.string }, render: function() { return <div>Hello Bob</div>; } }); var Hello = createReactClass({ propTypes: { firstname: PropTypes.string.isRequired, middlename: PropTypes.string.isRequired, // middlename is never used below lastname: PropTypes.string.isRequired }, render: function() { return <div>Hello {this.props.firstname} {this.props.lastname}</div>; } }); // good var Hello = createReactClass({ propTypes: { name: PropTypes.string }, render: function() { return <div>Hello {this.props.name}</div>; } });
-
2.3.3【参考】 props,state 优先使用解构赋值。eslint: react/destructuring-assignment
// bad const MyComponent = (props) => { return (<div id={props.id} />) }; // good const MyComponent = ({id}) => { return (<div id={id} />) }; const MyComponent = (props, context) => { const { id } = props; return (<div id={id} />) };
-
2.3.4【强制】prop 值为 true 时,可以省略它的值。eslint: react/jsx-boolean-value
// bad <Foo hidden={true} /> // good <Foo hidden />
-
2.3.5【推荐】prop 需要 propTypes 验证。eslint: react/prop-types
PropTypes 验证接收到的数据从而提高组件的可重用性。如果其他开发传入了不正确数据类型,可以及时警告。
// bad class Greeting extends React.Component { render() { return ( <h1>Hello, {this.props.name}</h1> ); } } // good class Greeting extends React.Component { render() { return ( <h1>Hello, {this.props.name}</h1> ); } } Greeting.propTypes = { name: PropTypes.string };
-
2.3.6【推荐】不要使用模糊的类型检查器。eslint: react/forbid-prop-types
不要使用模糊的类型验证,比如 any, array, object。它们可以用其他明确的类型代替。any可以替换为任意类型,array 和 object 可以分别替换为 arrayOf 和 shape。
// bad class MyComponent extends React.Component { ... } MyComponent.propTypes = { // 任意类型的数据 optionalAny: PropTypes.any, // 一个未指定元素类型的数组 optionalArray: PropTypes.array, // 一个未指定属性类型的对象 optionalObject: PropTypes.object }; // good class MyComponent extends React.Component { ... } MyComponent.propTypes = { // 指明待验证数据的特性类型,确保接收的数据是有效的 optionalAny: PropTypes.string, requiredAny: PropTypes.any.isRequired, // 一个指定了元素类型的数组 optionalArray: PropTypes.arrayOf(PropTypes.number), // 一个指定了属性类型的对象 optionalObject: PropTypes.shape({ color: PropTypes.string, fontSize: PropTypes.number }), };
-
2.3.7【参考】属性需要指定 defaultProps,除了 isRequired 的属性。eslint: react/require-default-props
// bad function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = { foo: PropTypes.number.isRequired, bar: PropTypes.string, }; // good function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = { foo: PropTypes.number.isRequired, bar: PropTypes.string, }; MyStatelessComponent.defaultProps = { bar: '', };
-
2.3.8【强制】如果属性有 isRequired 类型检查,不要在 defaultProps 内对其赋值。eslint: react/default-props-match-prop-types
propTypes 类型检查发生在 defaultProps 解析之后,如果在 defaultProps 赋值,isRequired 类型检查没有实际意义。
// bad MyStatelessComponent.propTypes = { foo: React.PropTypes.string.isRequired, bar: React.PropTypes.string }; MyStatelessComponent.defaultProps = { foo: "foo" }; // good MyStatelessComponent.propTypes = { foo: React.PropTypes.string.isRequired, bar: React.PropTypes.string }; MyStatelessComponent.defaultProps = { bar: 'some default' };
-
2.3.9【推荐】不要用数组的索引值作为 map 生成元素的 key。eslint: react/no-array-index-key
为什么?React 使用 key 来标识哪些项已更改,已添加或已删除, key 应该始终稳定。使用不稳定的 ID 是一种反模式,因为它不能唯一标识元素。如果数组重新排序或将元素添加到数组的开头,可能会更改索引导致不必要的渲染,对性能产生负面影响。
如果数组的顺序可能发生变化,我们不建议使用索引值作为 key。
// bad {todos.map((todo, index) => <Todo {...todo} key={index} /> )} // good {todos.map(todo => ( <Todo {...todo} key={todo.id} /> ))}
-
2.3.10【强制】禁止将 children 作为属性名。eslint: react/no-children-prop
使用 JSX 时,
children
应嵌套在开始和结束标签之间。不使用JSX时,应将children
作为附加参数传递给React.createElement
。// bad <div children='Children' /> <MyComponent children={<AnotherComponent />} /> <MyComponent children={['Child 1', 'Child 2']} /> React.createElement("div", { children: 'Children' }) // good <div>Children</div> <MyComponent>Children</MyComponent> <MyComponent> <span>Child 1</span> <span>Child 2</span> </MyComponent> React.createElement("div", {}, 'Children') React.createElement("div", 'Child 1', 'Child 2')
-
2.3.11【强制】不要声明重复的属性名。eslint: react/jsx-no-duplicate-props
// bad <Hello name="John" name="John" />; // good <Hello firstname="John" lastname="Doe" />;
-
2.3.12【强制】style 的属性值必须是一个对象。eslint: react/style-prop-object
// bad <div style="color: 'red'" /> <div style={true} /> <Hello style={true} /> const styles = true; <div style={styles} /> React.createElement("div", { style: "color: 'red'" }); React.createElement("div", { style: true }); React.createElement("Hello", { style: true }); const styles = true; React.createElement("div", { style: styles }); // good <div style={{ color: "red" }} /> <Hello style={{ color: "red" }} /> const styles = { color: "red" }; <div style={styles} /> React.createElement("div", { style: { color: 'red' }}); React.createElement("Hello", { style: { color: 'red' }}); const styles = { height: '100px' }; React.createElement("div", { style: styles });
-
2.3.13【推荐】不要单独使用 target='_blank'。eslint: react/jsx-no-target-blank
target='_blank'
常用于在新标签页打开。使用这个属性可能造成严重的安全问题。建议和rel='noreferrer noopener'
一起使用。详见// bad const Hello = <a target='_blank' href="http://example.com/"></a> const Hello = <a target='_blank' href={ dynamicLink }></a> // good const Hello = <p target='_blank'></p> const Hello = <a target='_blank' rel='noopener noreferrer' href="http://example.com"></a> const Hello = <a target='_blank' href="relative/path/in/the/host"></a> const Hello = <a target='_blank' href="/absolute/path/in/the/host"></a> const Hello = <a></a>
-
2.4.1【强制】不要在 setState 中使用 this.state。eslint: react/no-access-state-in-setstate
在 setState 中使用 this.state 可能导致错误,当两个 state 在同一个批处理中时,引用的是旧状态而不是新状态。 为避免这种情况,请在回调中使用 preState 作为第一个参数。
// bad function increment() { this.setState({ value: this.state.value + 1 }); } // good function increment() { this.setState(prevState => ({ value: prevState.value + 1 })); }
bad case 中假设 value 为1,有两个 setState 操作在同一个批处理中执行,实际执行的是:
setState({ value: 1 + 1 }) setState({ value: 1 + 1 })
good case 中 react 会以正确的更新后的状态调用参数。实际执行的是:
setState({ value: 1 + 1 }) setState({ value: 2 + 1 })
-
2.4.2【强制】声明的 state 必须被使用。eslint: react/no-unused-state
声明而未使用的 state 可能带来潜在的问题,也会给维护者造成困扰,应将它们删除。
// bad class MyComponent extends React.Component { state = { foo: 0 }; render() { return <SomeComponent />; } } var UnusedGetInitialStateTest = createReactClass({ getInitialState: function() { return { foo: 0 }; }, render: function() { return <SomeComponent />; } }) // good class MyComponent extends React.Component { state = { foo: 0 }; render() { return <SomeComponent foo={this.state.foo} />; } } var UnusedGetInitialStateTest = createReactClass({ getInitialState: function() { return { foo: 0 }; }, render: function() { return <SomeComponent foo={this.state.foo} />; } })
-
2.5.1【强制】使用 ref 回调函数或 React.createRef(),不要使用字符串。eslint: react/no-string-refs
// bad - 使用字符串 class MyComponent extends React.Component { componentDidMount() { this.refs.inputRef.focus(); } render() { return <input type="text" ref="inputRef" />; } } // good - 使用回调函数 class MyComponent extends React.Component { componentDidMount() { this.inputRef.focus(); } render() { return <input type="text" ref={(ele) => { this.inputRef = ele; }} />; } } // good - 使用 React.createRef(),React V16 后版本支持 class MyComponent extends React.Component { constructor(props) { super(props); this.inputRef = React.createRef(); } componentDidMount() { this.inputRef.current.focus(); } render() { return <input type="text" ref={this.inputRef} />; } }
-
2.6.1【参考】组件方法的排序规则。eslint: react/sort-comp
React 组件内有声明周期方法、事件处理方法、render 方法等几类方法,指定这些方法按固定的顺序排序可以增强代码的一致性,方便查找和阅读。
我们推荐的方法排序如下:
- 可选的
static
方法 constructor
getChildContext
componentWillMount
componentDidMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
componentDidUpdate
componentWillUnmount
- clickHandlers 或 eventHandlers 比如
onClickSubmit()
或onChangeDescription()
render
的 getter 方法 比如getSelectReason()
或getFooterContent()
- 可选的 render 方法 比如
renderNavigation()
或renderProfilePicture()
render
- 可选的
-
2.7.1【强制】不要使用 mixins。
Mixins 引入了隐式依赖,可能导致命名冲突,并导致滚雪球式的复杂度。大多数使用 mixin 的场景都可以通过组件、高阶组件或工具模块以更好的方式完成。
-
3.1【强制】文件扩展名: 使用 .jsx、.tsx、.js 或 .ts 作为 React 组件的文件扩展名。eslint: react/jsx-filename-extension
-
3.2【强制】引用名:使用大驼峰风格命名引用的组件,使用小驼峰风格命名引用组件的实例。eslint: react/jsx-pascal-case
// bad import reservationCard from './reservation-card'; // good import ReservationCard from './reservation-card'; // bad const ReservationItem = <ReservationCard />; // good const reservationItem = <ReservationCard />;
-
3.3【推荐】高阶组件命名:将高阶组件名和传入组件名组合作为 displayName。
例如,高阶组件
withFoo()
,当传入组件Bar
时,应该产生一个组件,应使用 withFoo(Bar) 作为生成组件的 displayName。组件的
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; }
-
4.1【强制】只在最顶层调用 Hooks,不要在循环、条件和嵌套函数中调用 Hooks。eslint: rules of Hooks - only call Hooks at the top level
// bad - call Hooks inside conditions function ComponentWithConditionalHook() { if (cond) { useConditionalHook(); } } // bad - call Hooks inside loops function ComponentWithHookInsideLoop() { while (cond) { useHookInsideLoop(); } } // bad - call Hooks inside callback function ComponentWithHookInsideCallback() { useEffect(() => { useHookInsideCallback(); }); } // good function ComponentWithHook() { useHook(); }
-
4.2【强制】Hooks 命名必须以
use
开头,小驼峰形式// bad const customHook = () => {} // good const useCustomHook = () => {}
-
4.3【强制】只在 React 函数组件和自定义 Hooks 中调用 Hooks,不能在普通的 JavaScript 函数中调用 Hooks。eslint: rules of Hooks - only call Hooks from React functions
// bad - call Hooks inside class componennt class ClassComponentWithHook extends React.Component { render() { React.useState(); } } // bad - call Hooks inside normal function function normalFunctionWithHook() { useHookInsideNormalFunction(); } // good - call Hooks inside function component function ComponentWithHook() { useHook(); } // good - call Hooks inside custom Hooks function useHookWithHook() { useHook(); }
-
4.4【推荐】
useEffect
及类似 Hooks 需要声明所有依赖。eslint: exhaustive-deps此规则在某些场景下可能过于严格,并且 ESLint autofix 可能会造成一些问题,因此需注意:
- 升级
eslint-plugin-react-hooks
到 2.4.0 版本及以上,因为 2.4.0 版本后该规则的 autofix 被默认禁用 - 如果某些场景下此规则确实不适用,可以通过 ESLint 行注释手动禁用此规则,在行尾添加:
// eslint-disable-line react-hooks/exhaustive-deps
// bad function MyComponent() { const local = {}; useEffect(() => { console.log(local); }, []); } // good function MyComponent() { const local = {}; useEffect(() => { console.log(local); }, [local]); }
- 升级
无障碍丰富互联网应用规范(WAI-ARIA,简称 ARIA)是 W3C 发布的技术规范,定义了一组可用于元素的 HTML 特性,作为对 HTML 语义化的补充,让残障人士能更加便利的访问 Web 内容和使用 Web 应用。
本章节会涉及到一些 WAI-ARIA 规范中的术语:
- 角色(role):定义了元素的种类。如
role="button"
告诉屏幕阅读器这是一个按钮元素。 - 属性(property):通过给元素定义一些属性,让他们具备更多的语义。例如
aria-required="true"
意味着该元素在表单上是必填的。 - 状态(state):用于表达元素当前的条件的特殊属性,例如
aria-disabled="true"
,屏幕阅读器就会禁止编辑这个表单元素。状态和属性的差异之处就是:属性在应用的生命周期中不会改变,而状态可以,通常我们用编程的方法改变它,例如 JavaScript。
这篇文档对 WAI-ARIA 规范的内容和使用做了初步介绍。
-
5.1【推荐】img 标签应包含 alt 属性。eslint: jsx-a11y/alt-text
如果图片无需被无障碍阅读器识别(如作为 button 的 icon 使用),你可以将
alt
属性写为空字符串// bad <img src="hello.jpg" /> // good <img src="hello.jpg" alt="Me waving hello" /> // good - 图片无需被无障碍阅读器识别时 <button> <img src="icon.png" alt="" /> Save </button>
-
5.2【推荐】img 标签的 alt 属性不要使用 "image","photo","picture" 之类的关键词。eslint: jsx-a11y/img-redundant-alt
屏幕阅读器已会将
img
元素识别成图片,再在 alt 中包含这类关键词没有意义。// bad <img src="hello.jpg" alt="Picture of me waving hello" /> // good <img src="hello.jpg" alt="Me waving hello" />
-
5.3【推荐】锚元素(即
<a>
元素)必须含有内容,且内容必须对屏幕阅读器可见(这里指内容不能通过设置aria-hidden
属性隐藏)。eslint: jsx-a11y/anchor-has-content// bad - empty content <a /> // bad - content not accessible to screen readers <a><TextWrapper aria-hidden /></a> // good <a>Anchor Content!</a> <a><TextWrapper /><a>
-
5.4【推荐】禁止使用无效的 ARIA 属性,只能使用列在 WAI-ARIA States and Properties spec 中的
aria-*
属性。eslint: jsx-a11y/aria-props// bad - Labeled using incorrectly spelled aria-labeledby <div id="address_label">Enter your address</div> <input aria-labeledby="address_label"> // good - Labeled using correctly spelled aria-labelledby <div id="address_label">Enter your address</div> <input aria-labelledby="address_label">
-
5.5【推荐】ARIA 属性、状态的值必须为有效值。eslint: jsx-a11y/aria-proptypes
// bad - the aria-hidden state is of type true/false <span aria-hidden="yes">foo</span> // good <span aria-hidden="true">foo</span>
-
5.6【推荐】禁止特定元素包含
role
和aria-*
属性。一些保留的 DOM 元素不支持设置 ARIA 角色或者属性,通常是因为这些元素是不可见的,例如meta, html, script, style
。eslint: jsx-a11y/aria-unsupported-elements// bad - the meta element should not be given any ARIA attributes <meta charset="UTF-8" aria-hidden="false" /> // good <meta charset="UTF-8" />
-
5.7【推荐】仅使用有效的、非抽象的 ARIA roles,了解更多。eslint: jsx-a11y/aria-role
// bad - not an ARIA role <div role="datepicker" /> // bad - abstract ARIA role <div role="range" /> // good <div role="button" />
-
5.8【推荐】有 ARIA role 的元素必须也声明该 role 需要的属性,了解更多。eslint: jsx-a11y/role-has-required-aria-props
// bad - the checkbox role requires the aria-checked state <span role="checkbox" aria-labelledby="foo" tabindex="0"></span> // good - the checkbox role requires the aria-checked state <span role="checkbox" aria-checked="false" aria-labelledby="foo" tabindex="0"></span>
-
5.9【推荐】强制拥有显式或隐式 role 的元素,只能含有该 role 支持的
aria-*
属性。eslint: jsx-a11y/role-supports-aria-props一些元素会有隐式的 role ,譬如
<a href="#">
,会被解析为role="link"
。很多 ARIA 属性只能在具有特定 role 的元素上使用// bad - the radio role does not support the aria-required property <ul role="radiogroup" aria-labelledby="foo"> <li aria-required tabIndex="-1" role="radio" aria-checked="false">Rainbow Trout</li> <li aria-required tabIndex="-1" role="radio" aria-checked="false">Brook Trout</li> <li aria-required tabIndex="0" role="radio" aria-checked="true">Lake Trout</li> </ul> // good - the radiogroup role does support the aria-required property <ul role="radiogroup" aria-required aria-labelledby="foo"> <li tabIndex="-1" role="radio" aria-checked="false">Rainbow Trout</li> <li tabIndex="-1" role="radio" aria-checked="false">Brook Trout</li> <li tabIndex="0" role="radio" aria-checked="true">Lake Trout</li> </ul>
-
5.10【推荐】
<iframe>
元素必须有一个唯一的 title 属性,表示其内容。eslint: jsx-a11y/iframe-has-title// bad <iframe /> <iframe {...props} /> <iframe title="" /> <iframe title={undefined} /> <iframe title={false} /> <iframe title={true} /> <iframe title={42} /> // good <iframe title="This is a unique title" />
-
5.11【推荐】不要使用 accessKey 属性。accessKey 属性提供了为当前元素生成快捷键的方式,不过 accessKey 值可能与系统或浏览器键盘快捷键或辅助技术功能相冲突,所以不建议使用。eslint: jsx-a11y/no-access-key
// bad <div accessKey="h" /> // good <div />
-
5.12【推荐】禁止使用会造成视觉分散的元素。一些会引起视觉注意力分散的元素对视觉障碍的用户会造成问题,例如
<marquee>
和<blink>
。eslint: jsx-a11y/no-distracting-elements// bad <marquee /> <blink /> // good <div />
-
5.13【推荐】scope 属性只能在
<th>
元素上使用,了解更多。eslint: jsx-a11y/scope// bad <div scope /> // good <th scope="col" />
- eslint-config-ali:本规约配套的 ESLint 规则包,可使用
eslint-config-ali/react
或eslint-config-ali/typescript/react
引入本文介绍的规则(「无障碍」章节的规则未默认引入,如需引入请参考文档中的「a11y 支持」章节)