Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React 编码规范 #7

Open
SyMind opened this issue Jan 28, 2021 · 0 comments
Open

React 编码规范 #7

SyMind opened this issue Jan 28, 2021 · 0 comments
Labels

Comments

@SyMind
Copy link
Owner

SyMind commented Jan 28, 2021

前言

规则来源于

  1. eslint-plugin-react 提供的规则,选择出有利于我们当前项目的部分;
  2. React 社区中的讨论。

愿望

  1. 每一条规则都应有详尽的解释、示例代码,能直观看到规则的作用、得知使用该规则的原因;
  2. 每一条规则都可以使用 eslint 进行自动化检查;
  3. 已有项目中完全没有违反的规则无需列出。

JSX

[强制] 没有子节点的组件使用自闭合语法(react/self-closing-comp)

解释:

JSX与HTML不同,所有元素均可以自闭合。

// Bad
<Foo></Foo>
<div></div>

// Good
<Foo />
<div />

[强制] 保持起始和结束标签在同一层缩进(react/jsx-wrap-multilines)

解释:

代码样式。

// Bad
class Message {
    render() {
        return <div>
            <span>Hello World</span>
        </div>;
    }
}

// Good
class Message {
    render() {
        return (
            <div>
                <span>Hello World</span>
            </div>;
        );
    }
}

[强制] 自闭合标签的/>前添加一个空格(react/jsx-space-before-closing)

解释:

代码样式。

// Bad
<Foo bar="bar"/>
<Foo bar="bar"  />

// Good
<Foo bar="bar" />

API

[强制] 禁止为继承自 PureComponent 的组件编写 shouldComponentUpdate 实现(react/no-redundant-should-component-update)

解释:

在 React 的实现中,PureComponent 并不直接实现 shouldComponentUpdate,而是添加一个 isReactPureComponent 的标记,由 CompositeComponent 通过识别这个标记实现相关的逻辑。因此在 PureComponent 上自定义 shouldComponentUpdate 并无法享受 super.shouldComponentUpdate 的逻辑复用,也会使得这个继承关系失去意义。

相关的 issue:facebook/react#9239

补充:

  1. 为了避免组件无效的 render 导致的性能开销使用 PureComponet 和 memo 是好的,但请记住它们也是存在开销的,请勿滥用。

相关的 issue:facebook/react#14463

  1. 使用 react-redux 中 connect 方法包裹的组件没有继承 PureComponent 或使用 memo 方法的必要。

相关的代码:https://github.com/reduxjs/react-redux/blob/754c1059ded0c1ea3a9b6dc1d870e31c22d8c3b7/src/components/connectAdvanced.js#L443

  1. 可使用 React Developer Tools 中开启 Highlight updates when components render 设置项观察组件渲染情况。

[强制] 禁止使用 String 类型的 Refs(react/no-string-refs)

解释:

它已过时并可能会在 React 未来的版本被移除。

补充:

相关 issue:facebook/react#8333 (comment)

String 类型的 Refs 存在的问题:

  1. 它要求 React 跟踪当前渲染的组件,这会导致 React 的执行稍微慢一些。
  2. 它不能像大多数人期望的那样使用“渲染回调(render callback)”模式。
class MyComponent extends Component {
  renderRow = (index) => {
    // 这将无法工作,ref 将被添加到 DataTable 上,而非 MyComponent 上:
    return <input ref={'input-' + index} />;

    // This would work though! Callback refs are awesome.
    return <input ref={input => this['input-' + index] = input} />;
  }
 
  render() {
    return <DataTable data={this.props.data} renderRow={this.renderRow} />
  }
}
  1. 它是不可组合的,也就是说,如果库在传递的子对象上放一个 ref,用户就不能再放一个 ref。回调引用是可组合的。

[强制] 避免使用不安全的生命周期函数 componentWillMount、componentWillReceiveProps、componentWillUpdate(react/no-unsafe)

解释:

以上生命周期函数被 React 官方视为是不安全的,公司 react 规范中推荐使用 constructor 代替 componentWillMount,具体生命周期迁移参考『迁移过时的生命周期』。

备注:

React 官方视为以上生命周期不安全的原因是:对于 Concurrent 模式(实验性)这个未来特性,避免在 willMount / willUpdate 等生命周期挂钩中产生副作用非常重要。因为 React 当前的渲染分为 reconcile 和 commit 两个阶段,reconcile 阶段可以被高权重用户事件中端导致重复执行,由于以上生命周期函数在此阶段中被调用,导致这些生命周期函数存在被重复调用的可能。

React RFC 0006-static-lifecycle-methods:https://github.com/reactjs/rfcs/blob/master/text/0006-static-lifecycle-methods.md

React v16.9.0 更新日志:https://react.docschina.org/blog/2019/08/08/react-v16.9.0.html

迁移过时的生命周期:https://react.docschina.org/blog/2018/03/27/update-on-async-rendering.html#migrating-from-legacy-lifecycles

Concurrent 模式介绍 (实验性):https://react.docschina.org/docs/concurrent-mode-intro.html

(:з[__] Dan 宝在 stackoverflow 上的回答 - 在 React 中,我应该在 componentWillMount 还是 componentDidMount 中进行初始网络请求?https://stackoverflow.com/a/41612993

[强制] 禁止使用数组索引作为 key(react/no-array-index-key)

解释:

React 使用 key 来判断哪些元素已经改变、添加或删除。

补充:

在此多说一些,我想一定有人和我之前的一样对 key 存在这样的误解,key 的目的是为了更好的性能。不,这不是事实。

key 的主要目的是作为唯一标识,在 fiber diff 阶段能够正确地判断出元素改变、添加或删除状态,尤其是对自身拥有状态的组件,这非常重要。

而在此前提下,对于属性没有变更的组件,只有实现了 shouldComponentUpdate / memo 方法,才可以避免重复渲染。

相关 issue:facebook/react#1342 (comment)

Understanding the key prop: https://stackoverflow.com/questions/28329382/understanding-unique-keys-for-array-children-in-react-js/43892905#43892905

// Bad
function Posts() {
	const [list, setList] = useState([]);
	useEffect(() => {
		fetchPosts().then(setList);
	}, []);
	return list.map((post, index) => <input key={index} defaultValue={post.title} />);
}

// Good
function Posts() {
	const [list, setList] = useState([]);
	useEffect(() => {
		fetchPosts().then(setList);
	}, []);
	return list.map(post => <input key={post.id} defaultValue={post.title} />);
}


// Good - 如果没有 id 等唯一标识,可以由前端主动生成唯一标识
function Posts() {
	const [list, setList] = useState([]);
	useEffect(() => {
		fetchPosts().then(list => {
			for (const post of list) {
				post.id = SomeLibrary.generateUniqueID();
			}
			setList(list);
		});
	}, []);
	return list.map(post => <input key={post.id} defaultValue={post.title} />);
}

[建议] 避免在JSX的属性值中直接使用对象和函数表达式(react/jsx-no-bind)

解释:

PureComponent 使用 shallowEqual 对 props 和 state 进行比较来决定是否需要渲染,而在 JSX 的属性值中使用对象、函数表达式会造成每一次的对象引用不同,从而 shallowEqual 会返回 false,导致不必要的渲染。

补充:

函数组件中为避免子组件刷新,可使用 useCallback 和 useMemo 等 hook 来保持函数、对象类型的属性引用不变。

// Bad
class WarnButton {
    alertMessage(message) {
        alert(message);
    }
    render() {
        return <button type="button" onClick={() => this.alertMessage(this.props.message)}>提示</button>
    }
}

// Good
class WarnButton {
    @bind()
    alertMessage() {
        alert(this.props.message);
    }
    render() {
        return <button type="button" onClick={this.alertMessage}>提示</button>
    }
}

// Bad
function Foo() {
	async function handleOk() {
		try {
        	await fetch();
		} catch (error) {
			message.error('error');
			return;
		}
		message.success('success');
    }
	return <Modal onOk={handleOk} />;
}

// Good 
function Foo() {
	const handleOk = useCallback(async () => {
		try {
        	await fetch();
		} catch (error) {
			message.error('error');
			return;
		}
		message.success('success');
    }, []);

	return <Modal onOk={handleOk} />;
}

[建议] 禁止在函数组件上使用 defaultProps(react/require-default-props,ignoreFunctionalComponents: true)

解释:

defaultProps 在类中非常有用,因为 props 对象被传递给许多不同的方法,如生命周期、回调等。每一个都有自己的作用域。这导致使用 JS 默认参数变得困难,因为您必须在每个函数中反复确定相同的默认值。

class Foo {
  static defaultProps = {foo: 1};
  componentDidMount() {
    let foo = this.props.foo;
    console.log(foo);
  }
  componentDidUpdate() {
    let foo = this.props.foo;
    console.log(foo);
  }
  componentWillUnmount() {
    let foo = this.props.foo;
    console.log(foo);
  }
  handleClick = () => {
    let foo = this.props.foo;
    console.log(foo);
  }
  render() {
    let foo = this.props.foo;
    console.log(foo);
    return <div onClick={this.handleClick} />;
  }
}

但是,在函数组件中,实际上不需要这种模式,因为您可以只使用 JS 默认参数,并且通常这些值的所有使用的位置都在同一作用域内。

function Foo({foo = 1}) {
  useEffect(() => {
    console.log(foo);
    return () => {
      console.log(foo);
    };
  });
  let handleClick = () => {
    console.log(foo);
  };
  console.log(foo);
  return <div onClick={handleClick} />;
}

React 团队之后的计划为,当在没有 .prototype.isReactComponent 的组件上使用 defaultProps 时,createElement 将发出警告。这包括那些特殊的组件,如 forwardRefmemo

如果 props 整体进行传递,那么升级将变得困难,不过你总是可以在需要时对它进行重构。

function Foo({foo = 1, bar = "hello"}) {
let props = {foo, bar};
//...
}
补充:

React RFC 0000-create-element-changes:http://0000-create-element-changes.md/ https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md#detailed-design

[注意] 单页应用中应避免使用 location.href = 'url to jump' 进行应用内部跳转

解释:

location.href = 'url to jump' 会导致页面刷新导致重新请求静态资源。

// Bad
function Foo() {
	const handleClick = useCallback(() => {
		location.href = '/path/to/jump';
	});
	return <div onClick={handleClick}>点击</div>;
}


// Good
function Foo() {
    const history = useHistory();
	const handleClick = useCallback(() => {
        // 在 react-router 中使用其 history 对象进行跳转
		history.push('/path/to/jump');
	});
	return <div onClick={handleClick}>点击</div>;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant