React組件設計技巧

React組件設計

組件分類

展現組件和容器組件

展現組件 容器組件
關注事物的展現 關注事物如何工做
可能包含展現和容器組件,而且通常會有DOM標籤和css樣式 可能包含展現和容器組件,而且不會有DOM標籤和css樣式
經常容許經過this.props.children傳遞 提供數據和行爲給容器組件或者展現組件
對第三方沒有任何依賴,好比store 或者 flux action 調用flux action 而且提供他們的回調給展現組件
不要指定數據如何加載和變化 做爲數據源,一般採用較高階的組件,而不是本身寫,好比React Reduxconnect(), Relay的createContainer(), Flux UtilsContainer.create()
僅經過屬性獲取數據和回調 null
不多有本身的狀態,即便有,也是本身的UI狀態 null
除非他們須要的本身的狀態,生命週期,或性能優化纔會被寫爲功能組件 null

下面是一個可能會常常寫的組件,評論列表組件,數據交互和展現都放到了一個組件裏面。css

// CommentList.js
class CommentList extends React.Component {
  constructor() {
    super();
    this.state = { comments: [] }
  }
  componentDidMount() {
    $.ajax({
      url: "/my-comments.json",
      dataType: 'json',
      success: function(comments) {
        this.setState({comments: comments});
      }.bind(this)
    });
  }
  render() {
    return <ul> {this.state.comments.map(renderComment)} </ul>;
  }
  renderComment({body, author}) {
    return <li>{body}—{author}</li>;
  }
}

咱們對上面的組件進行拆分,把他拆分紅容器組件 CommentListContainer.js 和展現組件 CommentListhtml

// CommentListContainer.js
class CommentListContainer extends React.Component {
  constructor() {
    super();
    this.state = { comments: [] }
  }
  componentDidMount() {
    $.ajax({
      url: "/my-comments.json",
      dataType: 'json',
      success: function(comments) {
        this.setState({comments: comments});
      }.bind(this)
    });
  }
  render() {
    return <CommentList comments={this.state.comments} />;
  }
}


// CommentList.js
class CommentList extends React.Component {
  constructor(props) {
    super(props);
  }
  render() { 
    return <ul> {this.props.comments.map(renderComment)} </ul>;
  }
  renderComment({body, author}) {
    return <li>{body}—{author}</li>;
  }
}

優點:react

  • 展現和容器更好的分離,更好的理解應用程序和UI
  • 重用性高,展現組件能夠用於多個不一樣的state數據源
  • 展現組件就是你的調色板,能夠把他們放到單獨的頁面,在不影響應用程序的狀況下,讓設計師調整UI
  • 迫使你分離標籤,達到更高的可用性

有狀態組件和無狀態組件

下面是一個最簡單的無狀態組件的例子:ajax

function HelloComponent(props, /* context */) {
  return <div>Hello {props.name}</div>
}
ReactDOM.render(<HelloComponent name="Sebastian" />, mountNode)

能夠看到,本來須要寫「類」定義(React.createClass 或者 class YourComponent extends React.Component)來建立本身組件的定義(有狀態組件),如今被精簡成了只寫一個 render 函數。更值得一提的是,因爲僅僅是一個無狀態函數,React 在渲染的時候也省掉了將「組件類」 實例化的過程。算法

結合 ES6 的解構賦值,可讓代碼更精簡。例以下面這個 Input 組件:json

function Input({ label, name, value, ...props }, { defaultTheme }) {
  const { theme, autoFocus, ...rootProps } = props
  return (
    <label
      htmlFor={name}
      children={label || defaultLabel}
      {...rootProps}
    >
    <input
      name={name}
      type="text"
      value={value || ''}
      theme={theme || defaultTheme}
      {...props}
    />
  )}
Input.contextTypes = {defaultTheme: React.PropTypes.object};

無狀態組件不像上述兩種方法在調用時會建立新實例,它建立時始終保持了一個實例,避免了沒必要要的檢查和內存分配,作到了內部優化。segmentfault

無狀態組件不支持 "ref"

高階組件

高階組件經過函數和閉包,改變已有組件的行爲,本質上就是 Decorator 模式在 React 的一種實現。數組

當寫着寫着無狀態組件的時候,有一天突然發現須要狀態處理了,那麼無需完全返工:)
每每咱們須要狀態的時候,這個需求是能夠重用的。瀏覽器

高階組件加無狀態組件,則大大加強了整個代碼的可測試性和可維護性。同時不斷「誘使」咱們寫出組合性更好的代碼。性能優化

高階函數

function welcome() {
    let username = localStorage.getItem('username');
    console.log('welcome ' + username);
}

function goodbey() {
    let username = localStorage.getItem('username');
    console.log('goodbey ' + username);
}

welcome();
goodbey();

咱們發現兩個函數有一句代碼是同樣的,這叫冗餘唉。(平時可能會有一大段代碼的冗餘)。

下面咱們要寫一箇中間函數,讀取username,他來負責把username傳遞給兩個函數。

function welcome(username) {
    console.log('welcome ' + username);
}

function goodbey(username) {
    console.log('goodbey ' + username);
}

function wrapWithUsername(wrappedFunc) {
    let newFunc = () => {
        let username = localStorage.getItem('username');
        wrappedFunc(username);
    };
    return newFunc;
}

welcome = wrapWithUsername(welcome);
goodbey = wrapWithUsername(goodbey);

welcome();
goodbey();

好了,咱們裏面的 wrapWithUsername 函數就是一個「高階函數」。
他作了什麼?他幫咱們處理了 username,傳遞給目標函數。咱們調用最終的函數 welcome的時候,根本不用關心 username是怎麼來的。

觸類旁通的高階組件

下面是兩個冗餘的組件。

import React, {Component} from 'react'

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

    componentWillMount() {
        let username = localStorage.getItem('username');
        this.setState({
            username: username
        })
    }

    render() {
        return (
            <div>welcome {this.state.username}</div>
        )
    }
}

export default Welcome;
import React, {Component} from 'react'

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

    componentWillMount() {
        let username = localStorage.getItem('username');
        this.setState({
            username: username
        })
    }

    render() {
        return (
            <div>goodbye {this.state.username}</div>
        )
    }
}

export default Goodbye;

咱們能夠經過剛剛高階函數的思想來建立一箇中間組件,也就是咱們說的高階組件。

import React, {Component} from 'react'

export default (WrappedComponent) => {
    class NewComponent extends Component {
        constructor() {
            super();
            this.state = {
                username: ''
            }
        }

        componentWillMount() {
            let username = localStorage.getItem('username');
            this.setState({
                username: username
            })
        }

        render() {
            return <WrappedComponent username={this.state.username}/>
        }
    }

    return NewComponent
}
import React, {Component} from 'react';
import wrapWithUsername from 'wrapWithUsername';

class Welcome extends Component {

    render() {
        return (
            <div>welcome {this.props.username}</div>
        )
    }
}

Welcome = wrapWithUsername(Welcome);

export default Welcome;
import React, {Component} from 'react';
import wrapWithUsername from 'wrapWithUsername';

class Goodbye extends Component {

    render() {
        return (
            <div>goodbye {this.props.username}</div>
        )
    }
}

Goodbye = wrapWithUsername(Goodbye);

export default Goodbye;

看到沒有,高階組件就是把 username 經過 props 傳遞給目標組件了。目標組件只管從 props裏面拿來用就行了。

爲了代碼的複用性,咱們應該儘可能減小代碼的冗餘。

  1. 提取共享的state,若是有兩個組件都須要加載一樣的數據,那麼他們會有相同的 componentDidMount 函數。
  2. 找出重複的代碼,每一個組件中constructor 和 componentDidMount都幹着一樣的事情,另外,在數據拉取時都會顯示Loading... 文案,那麼咱們應該思考如何使用高階組件來提取這些方法。
  3. 遷移重複的代碼到高階組件
  4. 包裹組件,而且使用props替換state
  5. 儘量地簡化

組件開發基本思想

單功能原則

使用react時,組件或容器的代碼在根本上必須只負責一塊UI功能。

讓組件保持簡單

  • 若是組件根本不須要狀態,那麼就使用函數定義的無狀態組件。
  • 從性能上來講,函數定義的無狀態組件 > ES6 class 定義的組件 > 經過 React.createClass() 定義的組件。
  • 僅傳遞組件所須要的屬性。只有當屬性列表太長時,才使用{...this.props}進行傳遞。
  • 若是組件裏面有太多的判斷邏輯(if-else語句)一般意味着這個組件須要被拆分紅更細的組件或模塊。
  • 使用明確的命名可以讓開發者明白它的功能,有助於組件複用。

基本準則

  • shouldComponentUpdate中避免沒必要要的檢查.
  • 儘可能使用不可變數據類型(Immutable).
  • 編寫針對產品環境的打包配置(Production Build).
  • 經過Chrome Timeline來記錄組件所耗費的資源.
  • componentWillMount或者componentDidMount裏面經過setTimeOut或者requestAnimationFram來延遲執行那些須要大量計算的任務.

組件開發技巧

form表單裏的受控組件和不受控組件

受控組件

在大多數狀況下,咱們推薦使用受控組件來實現表單。在受控組件中,表單數據由 React 組件負責處理。下面是一個典型的受控組建。

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

設置表單元素的value屬性以後,其顯示值將由this.state.value決定,以知足React狀態的同一數據理念。每次鍵盤敲擊以後會執行handleChange方法以更新React狀態,顯示值也將隨着用戶的輸入改變。

對於受控組件來講,每一次 state(狀態)變化都會伴有相關聯的處理函數。這使得能夠直接修改或驗證用戶的輸入和提交表單。

不受控組件

由於不受控組件的數據來源是 DOM 元素,當使用不受控組件時很容易實現 React 代碼與非 React 代碼的集成。若是你但願的是快速開發、不要求代碼質量,不受控組件能夠必定程度上減小代碼量。不然。你應該使用受控組件。

通常狀況下不受控組件咱們使用ref來獲取DOM元素進行操做。

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.input.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" ref={(input) => this.input = input} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

組件條件判斷

三元函數組件判斷渲染

const sampleComponent = () => {
  return isTrue ? <p>True!</p> : <p>false!</p>
};

使用&&表達式替換沒必要要的三元函數

const sampleComponent = () => {
  return isTrue ? <p>True!</p> : <none/>
};
const sampleComponent = () => {
  return isTrue && <p>True!</p>
};

須要注意的是若是isTrue 爲 0 ,其實會轉換成 false,可是在頁面中顯示的時候,&&仍是會返回0顯示到頁面中。

多重嵌套判斷

// 問題代碼
const sampleComponent = () => {
  return (
    <div>
      {flag && flag2 && !flag3
        ? flag4
        ? <p>Blah</p>
        : flag5
        ? <p>Meh</p>
        : <p>Herp</p>
        : <p>Derp</p>
      }
    </div>
  )
};

解決方案:

  • 最佳方案: 將邏輯移到子組件內部
  • 使用IIFE(Immediately-Invoked Function Expression 當即執行函數)
  • 知足條件的時候使用return強制跳出函數
const sampleComponent = () => {
  const basicCondition = flag && flag2 && !flag3;
  if (!basicCondition) return <p>Derp</p>;
  if (flag4) return <p>Blah</p>;
  if (flag5) return <p>Meh</p>;
  return <p>Herp</p>
}

setState異步性

在某些狀況下,React框架出於性能優化考慮,可能會將屢次state更新合併成一次更新。正由於如此,setState其實是一個異步的函數。 若是在調用setState()函數以後嘗試去訪問this.state,你獲得的可能仍是setState()函數執行以前的結果。

可是,有一些行爲也會阻止React框架自己對於屢次state更新的合併,從而讓state的更新變得同步化。 好比: eventListeners, Ajax, setTimeout 等等。

React框架之因此在選擇在調用setState函數以後當即更新state而不是採用框架默認的方式,即合併屢次state更新爲一次更新,是由於這些函數調用(fetch,setTimeout等瀏覽器層面的API調用)並不處於React框架的上下文中,React沒有辦法對其進行控制。React在此時採用的策略就是及時更新,確保在這些函數執行以後的其餘代碼能拿到正確的數據(即更新過的state)。

解決setState函數異步的辦法?

根據React官方文檔,setState函數實際上接收兩個參數,其中第二個參數類型是一個函數,做爲setState函數執行後的回調。經過傳入回調函數的方式,React能夠保證傳入的回調函數必定是在setState成功更新this.state以後再執行。

this.setState({count: 1}, () => {
    console.log(this.state.count); // 1
})

React源碼中setState的實現

ReactComponent.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
    typeof partialState === 'function' ||
    partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
    'function which returns an object of state variables.'
  );
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
};

updater的這兩個方法,和React底層的Virtual Dom(虛擬DOM樹)的diff算法有緊密的關係,因此真正決定同步仍是異步的實際上是Virtual DOMdiff算法。

依賴注入

React中,想作依賴注入(Dependency Injection)其實至關簡單。能夠經過props來進行傳遞。可是,若是組件數量不少,而且組件嵌套層次很深的話,這種方式就不太合適。

高階組件

// inject.jsx
var title = 'React Dependency Injection';
export default function inject(Component) {
  return class Injector extends React.Component {
    render() {
      return (
        <Component
          {...this.state}
          {...this.props}
          title={ title }
        />
      )
    }
  };
}
// Title.jsx
export default function Title(props) {
  return <h1>{ props.title }</h1>;
}
// Header.jsx
import inject from './inject.jsx';
import Title from './Title.jsx';

var EnhancedTitle = inject(Title);
export default function Header() {
  return (
    <header>
      <EnhancedTitle />
    </header>
  );
}

context

React v16.3.0 以前的 Context:

var context = { title: 'React in patterns' };
class App extends React.Component {
  getChildContext() {
    return context;
  }
  // ...
}

App.childContextTypes = {
  title: PropTypes.string
};
class Inject extends React.Component {
  render() {
    var title = this.context.title;
  // ...
  }
}
Inject.contextTypes = {
  title: PropTypes.string
};

以前的 Context 做爲一個實驗性質的 API,直到 React v16.3.0 版本前都一直不被官方所提倡去使用,其主要緣由就是由於在子組件中使用 Context 會破壞 React 應用的分型架構。

這裏的分形架構指的是從理想的 React 應用的根組件樹中抽取的任意一部分都還是一個能夠直接運行的子組件樹。在這個子組件樹之上再包一層,就能夠將它無縫地移植到任意一個其餘的根組件樹中。

但若是根組件樹中有任意一個組件使用了支持透傳的 Context API,那麼若是把包含了這個組件的子組件樹單獨拿出來,由於缺乏了提供 Context 值的根組件樹,這時的這個子組件樹是沒法直接運行的。

而且他有一個致命缺陷:任何一箇中間傳遞的組件shouldComponentUpdate 函數返回false,組件都不會獲得更新。

新的Context Api

新的Context Api 採用聲明式的寫法,而且能夠透過shouldComponentUpdate 函數返回false的組件繼續向下傳播,以保證目標組件必定能夠接收到頂層組件 Context 值的更新,一舉解決了現有 Context API 的兩大弊端,也終於成爲了 React 中的第一級(first-class) API

新的 Context API 分爲三個組成部分:

  1. React.createContext 用於初始化一個 Context
  2. XXXContext.Provider做爲頂層組件接收一個名爲 valueprop,能夠接收任意須要被放入 Context 中的字符串,數字,甚至是函數。
  3. XXXContext.Consumer做爲目標組件能夠出如今組件樹的任意位置(在 Provider 以後),接收 children prop,這裏的 children 必須是一個函數(context => ())用來接收從頂層傳來的 Context
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton(props) {
  return (
    <ThemeContext.Consumer>
      {theme => <Button {...props} theme={theme} />}
    </ThemeContext.Consumer>
  );
}

事件處理中的this指向問題

class Switcher extends React.Component {
  constructor(props) {
    super(props);
    this.state = { name: 'React in patterns' };
  }
  render() {
    return (
      <button onClick={ this._handleButtonClick }>
        click me
      </button>
    );
  }

  _handleButtonClick() {
    console.log(`Button is clicked inside ${ this.state.name }`);
    // 將致使
    // Uncaught TypeError: Cannot read property 'state' of null
  }
}

咱們能夠經過下面三種方式簡單實現this指向的綁定:

  • constructor 中事先綁定 this._buttonClick = this._handleButtonClick.bind(this);
  • 調用時使用箭頭函數 <button onClick={ () => this._buttonClick() }>
  • ES7中的綁定操做符 <button onClick={ ::this._buttonClick() }>

給setState傳入回調函數

setState() 不只能接受一個對象,還能接受一個函數做爲參數呢,該函數接受該組件前一刻的 state 以及當前的 props 做爲參數,計算和返回下一刻的 state。

// assuming this.state.count === 0
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
// this.state.count === 1, not 3

this.setState((prevState, props) =&gt; ({
  count: prevState.count + props.increment
}));
// Passing object
this.setState({ expanded: !this.state.expanded });

// Passing function
this.setState(prevState =&gt; ({ expanded: !prevState.expanded }));

組件切換技巧

import HomePage from './HomePage.jsx';
import AboutPage from './AboutPage.jsx';
import UserPage from './UserPage.jsx';
import FourOhFourPage from './FourOhFourPage.jsx';

const PAGES = {
  home: HomePage,
  about: AboutPage,
  user: UserPage
};

const Page = (props) =&gt; {
  const Handler = PAGES[props.page] || FourOhFourPage;

  return &lt;Handler {...props} /&gt;
};

React style

組件分類

基礎組件, 佈局組件, 排版組件

給無狀態的純UI組件應用樣式

請保持樣式遠離那些離不開state的組件. 好比路由, 視圖, 容器, 表單, 佈局等等不該該有任何的樣式或者css class出如今組件上. 相反, 這些複雜的業務組件應該有一些帶有基本功能的無狀態UI組件組成.

class SampleComponent extends Component {
  render() {
    return (
      &lt;form onSubmit={this.handleSubmit}&gt;
        &lt;Heading children='Sign In'/&gt;
        &lt;Input
          name='username'
          value={username}
          onChange={this.handleChange}/&gt;
        &lt;Input
          type='password'
          name='password'
          value={password}
          onChange={this.handleChange}/&gt;
        &lt;Button
          type='submit'
          children='Sign In'/&gt;
      &lt;/form&gt;
    )
  }
}

// 表達組件(帶樣式)
const Button = ({
  ...props
  }) =&gt; {
  const sx = {
    fontFamily: 'inherit',
    fontSize: 'inherit',
    fontWeight: 'bold',
    textDecoration: 'none',
    display: 'inline-block',
    margin: 0,
    paddingTop: 8,
    paddingBottom: 8,
    paddingLeft: 16,
    paddingRight: 16,
    border: 0,
    color: 'white',
    backgroundColor: 'blue',
    WebkitAppearance: 'none',
    MozAppearance: 'none'
  }

  return (
    &lt;button {...props} style={sx}/&gt;
  )
}

樣式模塊(style module)

通常來講, 在組件內寫死(hard code)樣式應該是要被避免的. 這些有可能被不一樣的UI組件分享的樣式應該被分開放入對應的模塊中.

// 樣式模塊
export const white = '#fff';
export const black = '#111';
export const blue = '#07c';

export const colors = {
  white,
  black,
  blue
};

export const space = [
  0,
  8,
  16,
  32,
  64
];

const styles = {
  bold: 600,
  space,
  colors
};

export default styles
// button.jsx
import React from 'react'
import { bold, space, colors } from './styles'

const Button = ({
  ...props
  }) =&gt; {
  const sx = {
    fontFamily: 'inherit',
    fontSize: 'inherit',
    fontWeight: bold,
    textDecoration: 'none',
    display: 'inline-block',
    margin: 0,
    paddingTop: space[1],
    paddingBottom: space[1],
    paddingLeft: space[2],
    paddingRight: space[2],
    border: 0,
    color: colors.white,
    backgroundColor: colors.blue,
    WebkitAppearance: 'none',
    MozAppearance: 'none'
  };

  return (
    &lt;button {...props} style={sx}/&gt;
  )
};

樣式函數(Style Functions)

// Modular powers of two scale
const scale = [
  0,
  8,
  16,
  32,
  64
];

// 經過這個函數去取得一部分的樣式
const createScaledPropertyGetter = (scale) =&gt; (prop) =&gt; (x) =&gt; {
  return (typeof x === 'number' &amp;&amp; typeof scale[x] === 'number')
    ? {[prop]: scale[x]}
    : null
};
const getScaledProperty = createScaledPropertyGetter(scale);

export const getMargin = getScaledProperty('margin');
export const getPadding = getScaledProperty('padding');
// 樣式函數的用法
const Box = ({
  m,
  p,
  ...props
  }) =&gt; {
  const sx = {
    ...getMargin(m),
    ...getPadding(p)
  };

  return &lt;div {...props} style={sx}/&gt;
};

// 組件用法.
const Box = () =&gt; (
  &lt;div&gt;
    &lt;Box m={2} p={3}&gt;
      A box with 16px margin and 32px padding
    &lt;/Box&gt;
  &lt;/div&gt;
);

常見小坑

state不更新?

class SampleComponent extends Component {
  // constructor function (or getInitialState)
  constructor(props) {
    super(props);
    this.state = {
      flag: false,
      inputVal: props.inputValue
    };
  }

  render() {
    return &lt;div&gt;{this.state.inputVal &amp;&amp; &lt;AnotherComponent/&gt;}&lt;/div&gt;
  }
}

這樣作的危險在於, 有可能組件的props發生了改變可是組件卻沒有被更新. 新的props的值不會被React認爲是更新的數據由於構造器constructor或者getInitialState方法在組件建立以後不會再次被調用了,所以組件的state再也不會被更新。 要記住, State的初始化只會在組件第一次初始化的時候發生。

class SampleComponent extends Component {
  // constructor function (or getInitialState)
  constructor(props) {
    super(props);
    this.state = {
      flag: false
    };
  }

  render() {
    return &lt;div&gt;{this.props.inputValue &amp;&amp; &lt;AnotherComponent/&gt;}&lt;/div&gt;
  }
}

更乾淨的render函數?

更乾淨的render函數? 這個概念可能會有點讓人疑惑.

其實在這裏乾淨是指咱們在shouldComponentUpdate這個生命週期函數裏面去作淺比較, 從而避免沒必要要的渲染.

class Table extends PureComponent {
  render() {
    return (
      &lt;div&gt;
        {this.props.items.map(i =&gt;
          &lt;Cell data={i} options={this.props.options || []}/&gt;
        )}
      &lt;/div&gt;
    );
  }
}

這種寫法的問題在於{this.props.options || []} 這種寫法會致使全部的Cell都被從新渲染即便只有一個cell發生了改變. 爲何會發生這種事呢?

仔細觀察你會發現, options這個數組被傳到了Cell這個組件上, 通常狀況下, 這不會致使什麼問題. 由於若是有其餘的Cell組件, 組件會在有props發生改變的時候淺對比props而且跳過渲染(由於對於其餘Cell組件, props並無發生改變). 可是在這個例子裏面, 當optionsnull時, 一個默認的空數組就會被當成Props傳到組件裏面去. 事實上每次傳入的[]都至關於建立了新的Array實例. 在JavaScript裏面, 不一樣的實例是有不一樣的實體的, 因此淺比較在這種狀況下老是會返回false, 而後組件就會被從新渲染. 由於兩個實體不是同一個實體. 這就徹底破壞了React對於咱們組件渲染的優化.

const defaultval = [];  // &lt;---  也可使用defaultProps
class Table extends PureComponent {
  render() {
    return (
      &lt;div&gt;
        {this.props.items.map(i =&gt;
          &lt;Cell data={i} options={this.props.options || defaultval}/&gt;
        )}
      &lt;/div&gt;
    );
  }
}

仍是屢次從新渲染

class App extends PureComponent {
  render() {
    return &lt;MyInput
      onChange={e =&gt; this.props.update(e.target.value)}/&gt;;
  }
}
class App extends PureComponent {
  update(e) {
    this.props.update(e.target.value);
  }

  render() {
    return &lt;MyInput onChange={this.update.bind(this)}/&gt;;
  }
}

在上面的兩個壞實踐中, 每次咱們都會去建立一個新的函數實體. 和第一個例子相似, 新的函數實體會讓咱們的淺比較返回false, 致使組件被從新渲染. 因此咱們須要在更早的時候去bind咱們的函數.

class App extends PureComponent {
  constructor(props) {
    super(props);
    this.update = this.update.bind(this);
  }

  update(e) {
    this.props.update(e.target.value);
  }

  render() {
    return &lt;MyInput onChange={this.update}/&gt;;
  }
}

命名

引用命名

React模塊名使用帕斯卡命名,實例使用駱駝式命名

// bad
import reservationCard from './ReservationCard';

// good
import ReservationCard from './ReservationCard';

// bad
const ReservationItem = &lt;ReservationCard /&gt;;

// good
const reservationItem = &lt;ReservationCard /&gt;;

高階模塊命名

// bad
export default function withFoo(WrappedComponent) {
  return function WithFoo(props) {
    return &lt;WrappedComponent {...props} foo /&gt;;
  }
}

// good
export default function withFoo(WrappedComponent) {
  function WithFoo(props) {
    return &lt;WrappedComponent {...props} foo /&gt;;
  }

  const wrappedComponentName = WrappedComponent.displayName
    || WrappedComponent.name
    || 'Component';

  WithFoo.displayName = `withFoo(${wrappedComponentName})`;
  return WithFoo;
}

屬性命名

避免使用DOM相關的屬性來用做其餘的用途。

// bad
&lt;MyComponent style="fancy" /&gt;

// good
&lt;MyComponent variant="fancy" /&gt;

私有函數添加 _ 前綴?

在React模塊中,不要給所謂的私有函數添加 _ 前綴,本質上它並非私有的。

爲何?_ 下劃線前綴在某些語言中一般被用來表示私有變量或者函數。可是不像其餘的一些語言,在JS中沒有原生支持所謂的私有變量,全部的變量函數都是共有的。儘管你的意圖是使它私有化,在以前加上下劃線並不會使這些變量私有化,而且全部的屬性(包括有下劃線前綴及沒有前綴的)都應該被視爲是共有的。

Ordering React 模塊生命週期

class extends React.Component 的生命週期函數:
可選的 static 方法

  • constructor 構造函數
  • getChildContext 獲取子元素內容
  • componentWillMount 模塊渲染前
  • componentDidMount 模塊渲染後
  • componentWillReceiveProps 模塊將接受新的數據
  • shouldComponentUpdate 判斷模塊需不須要從新渲染
  • componentWillUpdate 上面的方法返回 true, 模塊將從新渲染
  • componentDidUpdate 模塊渲染結束
  • componentWillUnmount 模塊將從DOM中清除, 作一些清理任務

點擊回調或者事件處理器 如 onClickSubmit()onChangeDescription()

render 裏的 getter 方法 如 getSelectReason()getFooterContent()

可選的 render 方法 如 renderNavigation()renderProfilePicture()

render render() 方法

如何定義 propTypes, defaultProps, contextTypes, 等等其餘屬性...

import React from 'react';
import PropTypes from 'prop-types';

const propTypes = {
  id: PropTypes.number.isRequired,
  url: PropTypes.string.isRequired,
  text: PropTypes.string,
};

const defaultProps = {
  text: 'Hello World',
};

class Link extends React.Component {
  static methodsAreOk() {
    return true;
  }

  render() {
    return &lt;a href={this.props.url} data-id={this.props.id}&gt;{this.props.text}&lt;/a&gt;;
  }
}

Link.propTypes = propTypes;
Link.defaultProps = defaultProps;

export default Link;

原文地址:http://www.javashuo.com/article/p-symchzat-h.html

相關文章
相關標籤/搜索