[转] React 最佳实践——那些 React 没告诉你但很重要的事

前言:对很多 react 新手来说,网上能找到的资源大都是些简单的 tutorial ,它们能教会你如何使用 react ,但并不会告诉你怎么在实际项目中优雅的组织和编写 react 代码。用谷歌搜中文“ React 最佳实践”发现前两页几乎全都是同一篇国外文章的译文...所以我总结了下自己过去那个项目使用 React 踩过的一些坑,也整理了一些别人的观点,希望对部分 react 使用者有帮助。

React 与 AJAX

React只负责处理View这一层,它本身不涉及网络请求/AJAX,所以这里我们需求考虑两个问题:

  • 第一,用什么技术从服务端获取数据;
  • 第二,获取到的数据应该放在react组件的什么位置。

React官方提供了一种解决方案:Load Initial Data via AJAX

使用jQuery的Ajax方法,在一个组件的componentDidMount()中发ajax请求,拿到的数据存在组件自己的state中,并调用setState方法去更新UI。如果是异步获取数据,则在componentWillUnmount中取消发送请求。

如果只是为了使用jQuery的Ajax方法就引入整个jQuery库,既是一种浪费又加大了整个应用的体积。那我们还有什么其他的选择吗?事实上是有很多的:fetch()fetch polyfillaxios...其中最需要我们关注的是window.fetch(),它是一个简洁、标准化的javascript的Ajax API。在Chrome和Firefox中已经可以使用,如果需要兼容其他浏览器,可以使用fetch polyfill。

React官方文档只告诉了我们在一个单一组件中如何通过ajax从服务器端获取数据,但并没有告诉我们在一个完整的实际项目中到底应该把数据存在哪些组件中,这部分如果缺乏规范的话,会导致整个项目变得混乱、难以维护。下面给出三种比较好的实践:

1. 所有的数据请求和管理都存放在唯一的一个根组件

让父组件/根组件集中发送所有的ajax请求,把从服务端获取的数据统一存放在这个组件的state中,再通过props把数据传给子组件。这种方法主要是针对组件树不是很复杂的小型应用。缺点就是当组件树的层级变多了以后,需要把数据一层一层地传给子组件,写起来麻烦,性能也不好。

2. 设置多个容器组件专门处理数据请求和管理

其实跟第一种方法类似,只不过设置多个容器组件来负责数据请求和状态管理。这里我们需要区分两种不同类型的组件,一种是展示性组件(presentational component),另一种是容器性组件(container component)。展示性组件本身不拥有任何状态,所有的数据都从容器组件中获得,在容器组件中发送ajax请求。两者更详细的描述,可以阅读下这篇文章:Presentational and Container Components

一个具体的例子:

假设我们需要展示用户的姓名和头像,首先创建一个展示性组件<UserProfile />,它接受两个Props:nameprofileImage。这个组件内部没有任何关于Ajax的代码。

然后创建一个容器组件<UserProfileContainer />,它接受一个userId的参数,发送Ajax请求从服务器获取数据存在state中,再通过props传给<UserProfile />组件。

3. 使用Redux或Relay的情况

Redux管理状态和数据,Ajax从服务器端获取数据,所以很显然当我们使用了Redux时,应该把所有的网络请求都交给redux来解决。具体来说,应该是放在Async Actions。如果用其他类Flux库的话,解决方式都差不多,都是在actions中发送网络请求。

Relay是Facebook官方推出的一个库。如果用它的话,我们只需要通过GraphQL来声明组件需要的数据,Relay会自动地把下载数据并通过props往下传递。不过想要用Relay,你得先有一个GraphQL的服务器...

一个标准组件的组织结构

1 class definition
    1.1 constructor
        1.1.1 event handlers
    1.2 ‘component‘ lifecycle events
    1.3 getters
    1.4 render
2 defaultProps
3 proptypes

示例:

class Person extends React.Component {
  constructor (props) {
    super(props);

    this.state = { smiling: false };

    this.handleClick = () => {
      this.setState({smiling: !this.state.smiling});
    };
  }

  componentWillMount () {
    // add event listeners (Flux Store, WebSocket, document, etc.)
  },

  componentDidMount () {
    // React.getDOMNode()
  },

  componentWillUnmount () {
    // remove event listeners (Flux Store, WebSocket, document, etc.)
  },

  get smilingMessage () {
    return (this.state.smiling) ? "is smiling" : "";
  }

  render () {
    return (
      <div onClick={this.handleClick}>
        {this.props.name} {this.smilingMessage}
      </div>
    );
  },
}

Person.defaultProps = {
  name: ‘Guest‘
};

Person.propTypes = {
  name: React.PropTypes.string
};

以上示例代码的来源:https://github.com/planningcenter/react-patterns#component-organization

使用 PropTypes 和 getDefaultProps()

  1. 一定要写PropTypes,切莫为了省事而不写
  2. 如果一个Props不是requied,一定在getDefaultProps中设置它

    React.PropTypes主要用来验证组件接收到的props是否为正确的数据类型,如果不正确,console中就会出现对应的warning。出于性能方面的考虑,这个API只在开发环境下使用。

基本使用方法:

propTypes: {
    myArray: React.PropTypes.array,
    myBool: React.PropTypes.bool,
    myFunc: React.PropTypes.func,
    myNumber: React.PropTypes.number,
    myString: React.PropTypes.string,

     // You can chain any of the above with `isRequired` to make sure a warning
    // is shown if the prop isn‘t provided.
    requiredFunc: React.PropTypes.func.isRequired
}

假如我们props不是以上类型,而是拥有复杂结构的对象怎么办?比如下面这个:

{
  text: ‘hello world‘,
  numbers: [5, 2, 7, 9],
}

当然,我们可以直接用React.PropTypes.object,但是对象内部的数据我们却无法验证。

propTypes: {
  myObject: React.PropTypes.object,
}

进阶使用方法:shape() 和 arrayOf()

propTypes: {
  myObject: React.PropTypes.shape({
    text: React.PropTypes.string,
    numbers: React.PropTypes.arrayOf(React.PropTypes.number),
  })
}

下面是一个更复杂的Props:

[
  {
    name: ‘Zachary He‘,
    age: 13,
    married: true,
  },
  {
    name: ‘Alice Yo‘,
    name: 17,
  },
  {
    name: ‘Jonyu Me‘,
    age: 20,
    married: false,
  }
]

综合上面,写起来应该就不难了:

propTypes: {
    myArray: React.PropTypes.arrayOf(
        React.propTypes.shape({
            name: React.propTypes.string.isRequired,
            age: React.propTypes.number.isRequired,
            married: React.propTypes.bool
        })
    )
}

把计算和条件判断都交给 render() 方法吧

1. 组件的state中不能出现props
 // BAD:
  constructor (props) {
    this.state = {
      fullName: `${props.firstName} ${props.lastName}`
    };
  }

  render () {
    var fullName = this.state.fullName;
    return (
      <div>
        <h2>{fullName}</h2>
      </div>
    );
  }
// GOOD:
render () {
  var fullName = `${this.props.firstName} ${this.props.lastName}`;
}

当然,复杂的display logic也应该避免全堆放在render()中,因为那样可能导致整个render()方法变得臃肿,不优雅。我们可以把一些复杂的逻辑通过helper function移出去。

// GOOD: helper function
renderFullName () {
  return `${this.props.firstName} ${this.props.lastName}`;
}

render () {
  var fullName = this.renderFullName();
}
2. 保持state的简洁,不要出现计算得来的state
// WRONG:
  constructor (props) {
    this.state = {
      listItems: [1, 2, 3, 4, 5, 6],
      itemsNum: this.state.listItems.length
    };
  }
  render() {
      return (
          <div>
              <span>{this.state.itemsNum}</span>
          </div>
      )
  }
// Right:
render () {
  var itemsNum = this.state.listItems.length;
}
3. 能用三元判断符,就不用If,直接放在render()里
// BAD:
renderSmilingStatement () {
    if (this.state.isSmiling) {
        return <span>is smiling</span>;
    }else {
        return ‘‘;
    }
},

render () {
  return <div>{this.props.name}{this.renderSmilingStatement()}</div>;
}
// GOOD:
render () {
  return (
    <div>
      {this.props.name}
      {(this.state.smiling)
        ? <span>is smiling</span>
        : null
      }
    </div>
  );
}
4. 布尔值都不能搞定的,交给IIFE吧

Immediately-invoked function expression

return (
  <section>
    <h1>Color</h1>
    <h3>Name</h3>
    <p>{this.state.color || "white"}</p>
    <h3>Hex</h3>
    <p>
      {(() => {
        switch (this.state.color) {
          case "red":   return "#FF0000";
          case "green": return "#00FF00";
          case "blue":  return "#0000FF";
          default:      return "#FFFFFF";
        }
      })()}
    </p>
  </section>
);
5. 不要把display logic写在componentWillReceivePropscomponentWillMount中,把它们都移到render()中去。

如何动态处理 classNames

1. 使用布尔值
// BAD:
constructor () {
    this.state = {
      classes: []
    };
  }

  handleClick () {
    var classes = this.state.classes;
    var index = classes.indexOf(‘active‘);

    if (index != -1) {
      classes.splice(index, 1);
    } else {
      classes.push(‘active‘);
    }

    this.setState({ classes: classes });
  }
// GOOD:
  constructor () {
    this.state = {
      isActive: false
    };
  }

  handleClick () {
    this.setState({ isActive: !this.state.isActive });
  }
2. 使用classnames这个小工具来拼接classNames:
// BEFORE:
var Button = React.createClass({
  render () {
    var btnClass = ‘btn‘;
    if (this.state.isPressed) btnClass += ‘ btn-pressed‘;
    else if (this.state.isHovered) btnClass += ‘ btn-over‘;
    return <button className={btnClass}>{this.props.label}</button>;
  }
});
// AFTER:
var classNames = require(‘classnames‘);

var Button = React.createClass({
  render () {
    var btnClass = classNames({
      ‘btn‘: true,
      ‘btn-pressed‘: this.state.isPressed,
      ‘btn-over‘: !this.state.isPressed && this.state.isHovered
    });
    return <button className={btnClass}>{this.props.label}</button>;
  }
});

未完待续...

时间: 2024-10-10 10:16:21

[转] React 最佳实践——那些 React 没告诉你但很重要的事的相关文章

腾讯优测优分享 | 探索react native首屏渲染最佳实践

腾讯优测是专业的移动云测试平台,旗下的优分享不定时提供大量移动研发及测试相关的干货~此文主要与以下内容相关,希望对大家有帮助. react native给了我们使用javascript开发原生app的能力,在使用react native完成兴趣部落安卓端发现tab改造后,我们开始对由react native实现的界面进行持续优化.目标只有一个,在享受react native带来的新特性的同时,在体验上无限逼近原生实现.作为一名前端开发,本文会从前端角度,探索react native首屏渲染最佳实

探索react native首屏渲染最佳实践

1.前言 react native给了我们使用javascript开发原生app的能力,在使用react native完成兴趣部落安卓端发现tab改造后,我们开始对由react native实现的界面进行持续优化.目标只有一个,在享受react native带来的新特性的同时,在体验上无限逼近原生实现.作为一名前端开发,本文会从前端角度,探索react native首屏渲染最佳实践. 2.首屏耗时计算方法 2.1我们关注的耗时 优化首屏渲染耗时,需要先定义首屏耗时的衡量方法.将react nat

总结 React 组件的三种写法 及最佳实践

React 专注于 view 层,组件化则是 React 的基础,也是其核心理念之一,一个完整的应用将由一个个独立的组件拼装而成. 截至目前 React 已经更新到 v15.4.2,由于 ES6 的普及和不同业务场景的影响,我们会发现目前主要有三种创建 React 组件的写法:1. ES5写法React.createClass,2. ES6写法React.Component,3. 无状态的函数式写法(纯组件-SFC). 你们最钟爱哪种写法呢?萝卜青菜各有所爱~ 每个团队都有自己的代码规范和开发模

react最佳入门实践(1)

1.环境搭建 React 是 Facebook 推出的一个用来构建用户界面的 JavaScript 库 1.1.安装node(mac版) 安装homebrew /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 通过homebrew安装node brew install node 测试是否安装正确 node -v npm -v 注意:wind

Immutable 详解及 React 中实践

本文转自:https://github.com/camsong/blog/issues/3 Shared mutable state is the root of all evil(共享的可变状态是万恶之源) -- Pete Hunt 有人说 Immutable 可以给 React 应用带来数十倍的提升,也有人说 Immutable 的引入是近期 JavaScript 中伟大的发明,因为同期 React 太火,它的光芒被掩盖了.这些至少说明 Immutable 是很有价值的,下面我们来一探究竟.

iOS React Native实践系列一

Facebook 在 React.js Conf 2015 大会上推出了基于 JavaScript 的开源框架 React Native React Native 结合了 Web 应用和 Native 应用的优势,可以使用 JavaScript 来开发 iOS 和 Android 原生应用.在 JavaScript 中用 React 抽象操作系统原生的 UI 组件,代替 DOM 元素来渲染等. React Native 使你能够使用基于 JavaScript 和 React 一致的开发体验在本地

React Native实践之携程Moles框架

编者:本文来自携程框架研发部高级经理魏晓军在第二期[携程技术微分享]上的分享,以下为整理后的文字实录.视频回放可点击这里.关注携程技术中心微信公号ctriptech,可获知更多微分享课程信息. 因为支持用javascript开发原生应用,React Native一推出就受到不少公司热捧,各家都跃跃欲试.但有一个痛点是,在移动端,我们是否有必要开发多套程序:iOS.Android和H5?本次将通过对Moles框架的分享,介绍携程在React Native方面的实战干货,希望给大家一些灵感和启发.

[转] iOS开发者的Weex伪最佳实践指北

[From] http://www.cocoachina.com/ios/20170601/19404.html 引子 这篇文章是笔者近期关于Weex在iOS端的一些研究和实践心得,和大家一起分享分享,也算是对学习成果的总结.文章里面提到的做法也许不是最佳实践,也许里面的方法称不算是一份标准的指南手册,所以标题就只好叫"伪最佳实践指北"了.有更好的方法欢迎大家一起留言讨论,一起学习. 由于笔者不太了解Android,所以以下的文章不会涉及到Android. 一. React Nativ

MySQL性能优化的21个最佳实践

今天,数据库的操作越来越成为整个应用的性能瓶颈了,这点对于Web应用尤其明显.关于数据库的性能,这并不只是DBA才需要担心的事,而这更是我们程序员需要去关注的事情.当我们去设计数据库表结构,对操作数据库时(尤其是查表时的SQL语句),我们都需要注意数据操作的性能.这里,我们不会讲过多的SQL语句的优化,而只是针对MySQL这一Web应用最多的数据库.希望下面的这些优化技巧对你有用. 1. 为查询缓存优化你的查询 大多数的MySQL服务器都开启了查询缓存.这是提高性最有效的方法之一,而且这是被My