不多教程會教你的:ReactJS最佳實踐

1 前言

我本身使用ReactJS有必定時間了,慢慢的也積累了不少所謂的Best Practice,以前內容比較少,都是記在腦子裏,後來就記在有道雲筆記裏,並且本來打算在團隊內部開分享會議的時候,在團隊內部分享的,但因爲種種緣由一直沒有機會,索性乾脆整理一下分享在社區了,也省的像以前用AngularJS的時候,有不少的心得在長時間不用以後慢慢都忘記了。正所謂好記性不如爛筆頭嘛,尤爲是時間久了。html

如下全部的代碼,內容幾乎都是ES6,若是你尚未開始用ES6代碼,能夠在準備好簡歷以後,惡狠狠的指着大家總監的鼻子羞辱他,由於我我的以爲離開ES6,ReactJS帶來的開發的便捷性會大打折扣。好不了解ES6的猿能夠去網上找阮一峯老師的在線教程,九淺一深,啊不!由淺入深的學習ES6。文章也假設你對React有必定的瞭解,知道React的各個生命週期階段的函數,但不要求精通。(這不廢話嘛!精通了還看這篇文章幹嗎。)react

2 奇淫技巧

2.1 context的妙用

2.1.1 使用場景

話很少說,看圖:
圖片描述express

圖片來源:https://segmentfault.com/a/11...segmentfault

有時候會有這樣一種狀況,組件嵌套層次很深,而最又內層組件又須要最外層組件的一個屬性,好比圖中D組件須要A組件中的一個屬性username,這時候最容易想到的就是:A組件內把這個username做爲props傳給B組件,B組件再原封不動的傳給C組件,C再傳給D,而B,C組件有可能都沒有用到這個username,只是做爲中間媒介在傳遞屬性。可是你依然不得不書寫這個props,針對這種狀況就能夠用到ReactJS中的context。數組

2.1.2 使用方法

// 組件A
class A extends Component {
    // 其餘代碼省略,只上關鍵代碼
    constructor(props) {
        super(props);
        this.state = {
            username: 'Neil'
        };
    }
    getChildContext() {
        return {
            username: this.state.username
        };
    }
}

A.childContextTypes = {
    username: PropTypes.string
};

// 組件D
class D extends Component {
    render() {
        return <div>{this.context.username}</div>
    }
}
D.contextChild = {
    username: PropTypes.string
}

能夠看到context的使用仍是比較簡單的,根組件A做爲Context Provider,須要在其中添加一個成員方法getChildContext(該方法必須返回一個對象),和一個靜態屬性childContextTypes。而後爲任何一個你想使用該context的子組件添加一個靜態屬性contextTypes來告訴React你要用哪一個contextless

2.1.3 注意事項

雖然使用比較簡單,可是官方也明確表示,並不推薦使用context,由於這會使得本來簡單並可控的React單向數據流變得複雜,不可預期。因此使用中仍是有如下幾點須要注意:ide

  • 推薦把不常常變更的值做爲context傳遞,好比在組件樹中,username就像一個全局常量同樣,不常常變更。
  • 在上面的例子中,組件使用context之後,在D組件的生命週期的這幾個函數中會有額外多餘的參數:函數

    • constructor(props, context)
    • componentWillReceiveProps(nextProps, nextContext)
    • shouldComponentUpdate(nextProps, nextState, nextContext)
    • componentWillUpdate(nextProps, nextState, nextContext)

在React15以前的本版本中,componentDidUpdate會有prevContext參數,可是最新的React16版本再也不接收此參數。性能

  • 在無狀態的函數式組件(Stateless Functional Components,關於什麼是函數式組件,後面會講到)中也可使用context,好比:
const D = (props, context) => {
    return <div>{context.username}</div>
};

D.contextChild = {username: PropTypes.string};
  • 儘可能不要更新contextcontext只適用於傳遞那些不常常變更的變量,可是若是你不得不更改context,例如上面的例子中A組件中的username實際上來自於A的state,假如咱們在A中調用this.setState({username: 'Michel'}), 那麼在D組件中context也會更新。由於A組件中聲明的getChildContext方法在A組件的初始化階段和更新階段都會被調用。但要注意的是,假如B組件或者C組件實現了shouldComponentUpdate,並在某些條件下返回了false,那麼,D組件中的context將不會再更新。由於此時,B組件或者C組件的render並不會從新被調用。D組件也就不會更新(換句話說,在D組件生命週期的更新階段,任何的hook函數都不會被調用)。因此咱們應極力避免更新context,由於這會使應用的數據流變得很是混亂,這背離了React的設計思想。

2.2 自定義組件ref屬性的使用

React組件基本分爲兩類,一類是原生的組件好比div,span,ul等等這類的HTML標籤(其實你也能夠直接叫他HTML DOM元素,只是我本身認爲他也是一類組件),一類是自定義組件,包括用class定義的組件和用函數定義的組件,ref屬性在這兩種組件中所表明的含義是不同的。學習

2.2.1 refHTML標籤上使用

看代碼:

class CustomComponent extends React.Component {
  render() {
    return (
      <div>
      <input type="text"
          ref={(input) => { this.textInput = input; }} />
      </div>
    );
  }
}

此時在CustomComponent組件內部的input標籤上有一個ref屬性,該屬性是一個匿名函數,該函數有一個形參,表明的是input這個標籤的DOM對象的引用,而後再函數體中將該對象賦值給this.textInput屬性。

2.2.2 ref在自定義組件上的應用

class Parent extends React.Component {
  render() {
    return (<Child ref={component => {this.child = component}}/>);
  }
}

class Child extends React.Component {
  render() {return (<div> I am a child.</div>);}
}

上例中,咱們在父組件Parent中調用了Child組件,Child組件上一樣傳遞了這個特殊的屬性ref,在匿名函數體內,一樣將函數的參數賦值給this.child,此時在控制檯中打印出this.child
圖片描述

能夠看到該對象就是組件實例化以後的對象,包含props, contextstate等屬性。這種特性能夠容許咱們訪問某個子組件的內部狀態

2.2.3 ref在函數式組件上的使用

ref屬性不能夠在函數式組件上使用,假如將上例的Child改成:

const Child = (props) => {
    return (<div> I am a child.</div>);
}

此時在Parent組件中便不能爲Child組件添加ref屬性,由於函數式組件沒有實例,可是能夠在函數式組件內部使用ref屬性,好比咱們在Child組件中使用ref

2.3 將某個props複製給state

之因此想把這個拿出來講一說是由於在我剛開始學React的時候,團隊的加拿大Leader在review我代碼的時候,看到我寫了

class CustomComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            username: props.username
        };
    }      
}

以後,要求我不要這麼寫,說這麼寫是不規範的。其實後來看官方文檔,這麼寫也是能夠的。只不過在組件的props.username更新以後,this.state.username並再也不更新。若是想保證二者值同步,能夠在組件的生命週期函數componentWillReceiveProps中更新state,由於在組件的更新階段,componentWillReceiveProps函數是惟一一個能夠在更新組件以前再次調用setState來更新組件狀態的地方,須要注意的是若是的組件的更新是由於某個地方調用setState,那麼在組件的更新階段這個函數並不會被調用,以免陷入死循環。

componentWillReceiveProps(nextProps) {
    this.setState({username: nextProps.username});
}

3 最佳實踐

3.1 避免直接在組件上寫用bind函數

不少React教程在講到React事件處理的時候,都會有相似的寫法:

class ButtonList extends Component {
    constructor(props) {
        super(props);
        this.state = {
            state: props.username
        };
    }
    
    handleClick() {
        const {count} = this.state;
        this.setState({count: count + 1});
    }
    
    render() {
        return (
          // 假設Button是咱們自定義的一個組件,這個組件須要一個onClick屬性,屬性類型必須爲函數
          <Button onClick={this.handleClick.bind(this)} />
        )    
    }
}

這樣的寫法並無錯,可是可有可能會引發一些性能問題,由於ButtonListrender函數每一次被調用的時候,都會建立一個新的函數,而後賦值給onClick屬性,在Button組件內部屬性的比較又是用===,在某些狀況下,這樣會引發Button組件沒必要要的從新渲染。深究其緣由是由於每次調用bind函數都會返回一個新的函數(還沒完全弄明白bind函數原理的猿能夠看這篇文章)。好比,下面的代碼:

function print() {
    console.log(this.a);
}
let obj = {a: 'A'}
print.bind(obj) === print.bind(obj);

最後一行代碼比較的結果始終是false,由於每次調用bind函數都會返回一個新的函數對象的引用。這就是爲何要儘可能避免在行內寫bind函數,<Button type='button' onClick={(event) => {}} />這樣的寫法也有相同的問題,由於這樣至關於每次都聲明一個新的匿名函數。最佳的寫法是在constructor內寫this.handleClick = this.handleClick.bind(this)(至關於在組件初始化時,就把bind函數返回的新函數保存在一個成員變量中,這樣,在組件的更新階段,每次給onClick屬性都傳遞的都是同一個函數的引用),並在組件上這樣寫<Button onClick={this.handleClick} />
可是,假如在演示組件ButtonList有十幾個函數須要綁定,你可能會抱怨,每添加一個事件處理函數,都要寫相似這樣的代碼,致使組件臃腫不堪。身爲一個有逼格的程序猿,怎麼能夠幹這種重複的機械運動呢!這時,你能夠這樣寫:

constructor() {
    // 省略其餘非關鍵代碼,並假設咱們已經聲明瞭如下事件處理函數
    this.bind([
      'eventHandler1',
      'eventHandler2',
      'eventHandler3',
      'eventHandler4',
      'eventHandler5'
    ])
}

bind(methodArray) {
    methodArray.forEach(method => this[method] = this[method].bind(this));
}

3.2 多用函數式組件(Stateless Functional Component, SFC)

3.2.1 使用方法

React中自定義組件,最簡單的寫法能夠是這樣:

function Custom(props) {
    // props以函數參數的形式傳遞
    return(<div>This is a functional component, {props.name}</div>);
}

也能夠用ES6語法這樣寫:

class Custom extend Component {
    render() {
        return(<div>This is a functional component</div>);
    }
}

這兩種寫法屬於不一樣類型的組件,前一種稱爲函數式組件,後一種稱爲class類組件。這兩種組件在使用上有一些區別,好比2.2.2小結講到的ref屬性的使用。這裏推薦使用第一種寫法。由於第一種寫法的代碼量更少,並且函數式組件也有更加優秀的性能表現(關於兩種組件的性能比較,我還沒找更多的證據,聽說可以減小不少無心義的檢測和內存分配)總之這樣寫,更加優雅(裝逼)啦。若是你在用ES6語法,前一種寫法還能夠是這樣:

const Custom = ({name}) => {
    return(<div>This is a functional component, {name}</div>);
};

這中寫法最迷人的地方在於代碼量不多,代碼易讀,看函數聲明很容知道該組件須要哪些屬性。

3.2.2 注意事項

函數式組件雖好,但不是任何狀況下都能使用函數式組件。這裏給兩條參照標準:

  1. 正如該組件的英文名稱:Stateless Functional Component,只有在組件沒有狀態的時候才適用。
  2. 函數式組件沒有生命週期的hook函數,若是你想調用任何一個生命週期鉤子函數,請使用類組件。

3.3 避免直接修改porps或者state

關於這條,不少人在一開始學習React的時候就被灌輸了這個概念:不能在組件內部修改porps ,修改state要用setState。可是在一些複雜的狀況下,你們每每會在不經意間犯了這兩個錯誤。看下面的代碼:

// 假設data是這樣的結構:
// data = [
//     {
//         name: 'Lily',
//         age: 19
//     },
//     {
//         name: 'Tom',
//         age: 20
//     }
// ];
const data = this.props.data;
data.push({name: 'Neil', age: 22});

包括個人同事在內,他們常常會寫相似這樣的代碼,而後困惑的跑來問我,爲何組件沒有按照預期更新,由於這樣的寫法,已經更新了props。這涉及到JS中一個很重的知識點:JS中將對象(Function和Array也是對象)賦值給某一個變量,本質上是把對象的引用賦值給這個變量,做爲函數的實參傳遞時也是同樣的道理(若是還不明白什麼是對象的引用,網上有不少教程解釋的很清楚,我比較懶,就不解釋這麼基礎的概念了)。好比下面的代碼:

let obj = {a: 1};
function test(obj) {
    obj.a = 2;
}
test(obj);
console.log(obj.a); // 打印出2

因此在組件內這樣的代碼:data.push({name: 'Neil', age: 22})實際上已經更改了實際上已經不經意間在子組件內修改了父組件的傳來的props。若是將示例中的代碼換成const data = this.state.data; data.push({name: 'Neil', age: 22});後也會有相同的問題。正確的寫法應該是:

// 利用ES6的spread語法
// Spread會返回一個新數組,並將新數組的引用賦值給變量data,
// 修改data也就不會影響this.props.data
const data = [...this.props.data];

// 或者不利用ES6你在ES5中也能夠這樣:
// concat函數也會返回一個新的數組
var data = [].concat(this.props.data);

// 若是this.props.data是一個對象(字面量對象,非數組對象或函數對象)
// assign函數也會返回一個新的對象。
var data = Object.assign({}, this.props.data);


data.push({name: 'Neil', age: 22});

4 最後

還有其餘的東西,因爲篇幅和時間有限沒來得及寫,之後有機會寫一寫。文章有任何錯誤之處,請不吝賜教或輕噴。附上參考文章及連接:

https://reactjs.org/
https://segmentfault.com/a/11...
http://www.react.express/
https://segmentfault.com/a/11...
http://www.admin10000.com/doc...

相關文章
相關標籤/搜索