[轉] React 最佳實務——那些 React 沒告訴你但很重要的事

來源:互聯網
上載者:User

標籤:cpp   esc   any   erp   reserve   問題:   provided   應該   person   

前言:對很多 react 新手來說,網上能找到的資源大都是些簡單的 tutorial ,它們能教會你如何使用 react ,但並不會告訴你怎麼在實際項目中優雅的組織和編寫 react 代碼。用Google搜中文“ 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 polyfill、axios...其中最需要我們關注的是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 render2 defaultProps3 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 functionrenderFullName () {  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()中去。如何動態處理 classNames1. 使用布爾值
// 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>;  }});

未完待續...

[轉] React 最佳實務——那些 React 沒告訴你但很重要的事

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.