资讯专栏INFORMATION COLUMN

React 最佳实践

lavnFan / 1173人阅读

摘要:本文针对技术栈,总结了一些最佳实践,对编写高质量的代码有一定的参考作用。二最佳实践说明多用如果组件是纯展示型的,不需要维护和生命周期,则优先使用。理解并遵循这些最佳实践,写出来的代码质量会有一定的保证。

欢迎关注我的公众号睿Talk,获取我最新的文章:

一、前言

在日常开发和 Code Review 的时候,常常会发现一些共性的问题,也有很多值得提倡的做法。本文针对 React 技术栈,总结了一些最佳实践,对编写高质量的代码有一定的参考作用。

二、最佳实践 & 说明

多用 Function Component

如果组件是纯展示型的,不需要维护 state 和生命周期,则优先使用 Function Component。它有如下好处:

代码更简洁,一看就知道是纯展示型的,没有复杂的业务逻辑

更好的复用性。只要传入相同结构的 props,就能展示相同的界面,不需要考虑副作用。

更小的打包体积,更高的执行效率

一个典型的 Function Component 是下面这个样子:

</>复制代码

  1. function MenuItem({menuId, menuText, onClick, activeId}) {
  2. return (
  3. {menuText}
  4. );
  5. };

多用 PureComponent

如果组件需要维护 state 或使用生命周期方法,则优先使用 PureComponent,而不是 Component。Component 的默认行为是不论 state 和 props 是否有变化,都触发 render。而 PureComponent 会先对 state 和 props 进行浅比较,不同的时候才会 render。请看下面的例子:

</>复制代码

  1. class Child extends React.Component {
  2. render() {
  3. console.log("render Child");
  4. return (
  5. {this.props.obj.num}
  6. );
  7. }
  8. }
  9. class App extends React.Component {
  10. state = {
  11. obj: { num: 1 }
  12. };
  13. onClick = () => {
  14. const {obj} = this.state;
  15. this.setState({obj});
  16. }
  17. render() {
  18. console.log("render Parent");
  19. return (
  20. 点我
  21. );
  22. }
  23. }

点击按钮后,Parent 和 Child 的 render 都会触发。如果将 Child 改为 PureComponent,则 Child 的 render 不会触发,因为 props 还是同一个对象。如果将 Parent 也改为 PureComponent,则 Parent 的 render 也不会触发了,因为 state 还是同一个对象。

遵循单一职责原则,使用 HOC / 装饰器 / Render Props 增加职责

比如一个公用的组件,数据来源可能是父组件传过来,又或者是自己主动通过网络请求获取数据。这时候可以先定义一个纯展示型的 Function Component,然后再定义一个高阶组件去获取数据:

</>复制代码

  1. function Comp() {
  2. ...
  3. }
  4. class HOC extends PureComponent {
  5. async componentDidMount() {
  6. const data = await fetchData();
  7. this.setState({data});
  8. }
  9. render() {
  10. return ();
  11. }
  12. }

组合优于继承

笔者在真实项目中就试过以继承的形式写组件,自己写得很爽,代码的复用性也很好,但最大的问题是别人看不懂。我将复用的业务逻辑和 UI 模版都在父类定义好,子类只需要传入一些参数,然后再覆盖父类的几个方法就好(render的时候会用到)。简化的代码如下:

</>复制代码

  1. class Parent extends PureComponent {
  2. componentDidMount() {
  3. this.fetchData(this.url);
  4. }
  5. fetchData(url) {
  6. ...
  7. }
  8. render() {
  9. const data = this.calcData();
  10. return (
  11. {data}
  12. );
  13. }
  14. }
  15. class Child extends Parent {
  16. constructor(props) {
  17. super(props);
  18. this.url = "http://api";
  19. }
  20. calcData() {
  21. ...
  22. }
  23. }

这样的写法从语言的特性和功能实现来说,没有任何问题,最大的问题是不符合 React 的组件编写习惯。父类或者子类肯定有一方是不需要实现 render 方法的,而一般我们看代码都会优先找 render 方法,找不到就慌了。另外就是搞不清楚哪些方法是父类实现的,哪些方法是子类实现的,如果让其他人来维护这份代码,会比较吃力。

继承会让代码难以溯源,定位问题也比较麻烦。所有通过继承实现的组件都可以改写为组合的形式。上面的代码就可以这样改写:

</>复制代码

  1. class Parent extends PureComponent {
  2. componentDidMount() {
  3. this.fetchData(this.props.url);
  4. }
  5. fetchData(url) {
  6. ...
  7. }
  8. render() {
  9. const data = this.props.calcData(this.state);
  10. return (
  11. {data}
  12. );
  13. }
  14. }
  15. class Child extends PureComponent {
  16. calcData(state) {
  17. ...
  18. }
  19. render() {
  20. }
  21. }

这样的代码是不是看起来舒服多了?

如果 props 的数据不会改变,就不需要在 state 或者组件实例属性里拷贝一份

经常会看见这样的代码:

</>复制代码

  1. componentWillReceiveProps(nextProps) {
  2. this.setState({num: nextProps.num});
  3. }
  4. render() {
  5. return(
  6. {this.state.num}
  7. );
  8. }

num 在组件中不会做任何的改变,这种情况下直接使用 this.props.num 就可以了。

避免在 render 里面动态创建对象 / 方法,否则会导致子组件每次都 render

</>复制代码

  1. render() {
  2. const obj = {num: 1}
  3. return(
  4. {...}} />
  5. );
  6. }

在上面代码中,即使 Child 是 PureComponent,由于 obj 和 onClick 每次 render 都是新的对象,Child 也会跟着 render。

避免在 JSX 中写复杂的三元表达式,应通过封装函数或组件实现

</>复制代码

  1. render() {
  2. const a = 8;
  3. return (
  4. {
  5. a > 0 ? a < 9 ? ... : ... : ...
  6. }
  7. );
  8. }

像上面这种嵌套的三元表达式可读性非常差,可以写成下面的形式:

</>复制代码

  1. f() {
  2. ...
  3. }
  4. render() {
  5. const a = 8;
  6. return (
  7. {
  8. this.f()
  9. }
  10. );
  11. }

多使用解构,如 Function Component 的 props

</>复制代码

  1. const MenuItem = ({
  2. menuId, menuText, onClick, activeId,
  3. }) => {
  4. return (
  5. ...
  6. );
  7. };

定义组件时,定义 PropTypes 和 defaultProps

例子如下:

</>复制代码

  1. class CategorySelector extends PureComponent {
  2. ...
  3. }
  4. CategorySelector.propTypes = {
  5. type: PropTypes.string,
  6. catList: PropTypes.array.isRequired,
  7. default: PropTypes.bool,
  8. };
  9. CategorySelector.defaultProps = {
  10. default: false,
  11. type: undefined,
  12. };

避免使用无谓的标签和样式

下面这种情况一般外层的div是多余的,可以将样式直接定义在组件内,或者将定制的样式作为参数传入。例外:当ServiceItem需要在多个地方使用,而且要叠加很多不一样的样式,原写法会方便些。

</>复制代码

  1. // bad
  2. // good
三、总结

本文列举了笔者在项目实战和 Code Review 过程中总结的 10 条最佳实践,当中的一些写法和原则只代表个人立场。理解并遵循这些最佳实践,写出来的代码质量会有一定的保证。如果你有不同的意见,或者有补充的最佳实践,欢迎留言交流。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/101570.html

相关文章

  • React 可视化开发工具 shadow-widget 最佳实践(上)

    摘要:上例的功能块定义了如下节点树入口节点是面板,结合该节点的函数书写特点,我们接着介绍最佳实践如何处理功能块之内的编程。 本文介绍 React + Shadow Widget 应用于通用 GUI 开发的最佳实践,只聚焦于典型场景下最优开发方法。分上、下两篇讲解,上篇概述最佳实践,介绍功能块划分。 showImg(https://segmentfault.com/img/bVWu3d?w=6...

    techstay 评论0 收藏0
  • TypeScript 3.0 + React + Redux 最佳实践

    摘要:首先声明这篇文章是想说明一下最新版本的的新特性带来的极大的开发体验提升而不是如何利用开发应用这个特性就是对的支持在的中有说明具体可以参考这里在版本之前我们在开发应用尤其是在配合一类库的时候经常用到诸如之类的封装而这些函数其实都可以用装饰器的 首先声明, 这篇文章是想说明一下最新版本的 TypeScript(3.0) 的新特性带来的极大的 React 开发体验提升. 而不是如何利用 Ty...

    CloudwiseAPM 评论0 收藏0
  • React Native项目时依赖管理的最佳实践

    摘要:此时会把当前路径作为一个本地,在全局路径下创建一个软链接。所有依赖于全局路径下的都必须是一个版本的,并没有提供多版本号依赖的解决方法。因此,还是建议选择一个常用的版本安装在全局路径,个别需求其他版本号的的项目,使用来配置局部依赖。 在实际开发过程中,经常需要同时运行和修改多个React Native工程,比如运行github上的开源项目以观察某种控件的实际效果。那么此时,各项目下的初始...

    AbnerMing 评论0 收藏0
  • React.js 最佳实践(2016)_链接修正版

    摘要:译者按最近依旧如火如荼相信大家都跃跃欲试我们团队也开始在领域有所尝试年应该是逐渐走向成熟的一年让我们一起来看看国外的开发者们都总结了哪些最佳实践年在全世界都有很多关于新的更新和开发者大会的讨论关于去年的重要事件请参考那么年最有趣的问题来了我 译者按:最近React(web/native)依旧如火如荼,相信大家都跃跃欲试,我们团队也开始在React领域有所尝试. 2016年应该是Reac...

    syoya 评论0 收藏0

发表评论

0条评论

lavnFan

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<