React發展歷程中找到問題

爲何要寫這個主題,是由於首先就是由於週會立了一個flag要說分享,可是我又不想去分享一些什麼類庫了,由於一些別的類庫對於咱們業務來講,並無什麼特別好的幫助,其次以前跟同事聊天裏面,他問了我一個問題「大家是怎麼作到對新庫,新API保持這種持續學習的熱情的」,我是這麼回答他的「由於業務」,哈哈,你們會以爲我在扯淡,但其實裏面是有道理的,一些新的api,新的庫其實就是爲了解決一些歷史問題,當你在當前環境下沒法解決一些複雜問題而頭疼,用一些繞來繞去的方式解決的時候,發現來了一個新的模式去解決,你就會保持興奮和激動。那此次就帶着思考的角度去講React的編年史,也但願各位可以從中複習或者有些小夥伴從沒接觸過一些 react歷史問題,也一樣看看爲何新的React會產生這些新的API來幫助開發者們。react

Earlier than 0.14.x (2015年)

早於0.14x 的時候,ES6尚未普及,因此你們建立一個React的類的時候,都是以函數調用的形式建立的,傳入對應的鍵值對函數執行相應的lifeCycle, state, 和 props。git

一個普通最基本,帶有props 以及 state的組件是以這樣的形式組織的github

var Counter = React.createClass({
	getInitialState: function() {
		return {
			count: 0
		}
	},
	getDefaultProps: function() {
	    return {
	    	name: 'Mary'
	    };
	},
	componentDidMount: function() {
		this.setState({
			count: this.state.count + 1
		})
	},
	handleClick: function() {
this.setState(function(preState) {
			return {
				count: preState.count + 1
			}
		})
	},
	render: function() {
		return (
			<div 
			  onClick={this.handleClick}
			>
				{this.state.count}
			</div>
		)
	}
})
複製代碼

JSX

Jsx和ES6版本是基本上沒有差別性的api

State

state的初始值是以initialState的函數調用返回值來建立的bash

handler Method

不用ES6: 方法調用和ES6版本有一個最大的區別,對於ES6來講,咱們都知道它必須手動bind this。可是對於React.CreateClass來講,是不須要的,緣由實際上是在舊版本的reactClass這個對象裏面,在初始化的時候,會遍歷全部的傳入到createClass的key value,默認的內置lifCycle都會走默認的行爲,可是那些全部不是lifeCycle的,都通通會通過一個判斷循環,源碼不貼了,你們能夠本身翻, [github.com/facebook/re…]架構

if (this.__reactAutoBindMap) {
  	bindAutoBindMethods(this);
}

function bindAutoBindMethods(component) {
  for (var autoBindKey in component.__reactAutoBindMap) {
    if (component.__reactAutoBindMap.hasOwnProperty(autoBindKey)) {
      var method = component.__reactAutoBindMap[autoBindKey];
      component[autoBindKey] = bindAutoBindMethod(
        component,
        method
      );
    }
  }
}
複製代碼

上面的this,就是整個ReactClass的上下文環境,至此,對於handler的autoBind就是這樣實現的。app

Es6: JS丟失this問題框架

var obj = {
a: 123,
	test: function() {
console.log(this.a)
	}
}
function render(func) {

    func()
}

render(obj.test)
複製代碼

階段一總結:僅僅是比較新舊語法建立的React實例,就已經能夠探討到2個小知識了

  • ES5React.createClass 的方法體爲何不須要bind this
  • ES6Class extends React.Component this 指針的問題

Class Extends

對於class OOP來講,最基本的就是符合里氏替換原則,里氏替換原則中說,任何基類能夠出現的地方,子類必定能夠出現。也就是,打個比方,有一個BaseHeader,其中一個子類集成了BaseHeader,叫作HeaderWithAvatar,那麼全部BaseHeader出現的地方都可以替換成HeaderWithAvatar,這就稱爲同一類型的組件。dom

首先能夠確定的是,用繼承來實現邏輯複用沒有問題,可是有侷限性ide

先看一下代碼

export class BaseHeaderInh extends React.Component {
  state = {
    classname: 'base'
  }
  componentDidMount() {
    console.log('shit')
  }
  render() {
    return (
      <div className={this.state.classname}>HeaderInh base</div>
    )
  }
}

export class HeaderInh extends BaseHeaderInh {
  componentDidMount() {
    console.log('bull shit')
  }
  componentWillReceiveProps(nextProps) {
    // code for base components/*  */
    console.log('base componentWillReceiveProps')
  }
  state = {
    classname: `${this.state.classname} red`
  }
}

export class HeaderReadAvatar extends HeaderInh {
  componentWillReceiveProps(nextProps) {
    super.componentWillReceiveProps()
    console.log('i want change somethin in domain props')
  }
  render() {
    return (
      <div>
        {super.render()}
        icon
      </div>
    )
  }
}
複製代碼
  • lifeCycle或者method的override會致使組件不符合預期運行
  • 嚴重耦合子類和父類的關係
  • 若是不仔細閱讀基類,徹底無法放心地實現子類特有的應用場景。
  • 必須當心翼翼地寫代碼

階段二總結:基於React組織方式的繼承邏輯複用有些什麼問題

對於React的哲學思想來講,但願開發者對於每一個組件都承擔着對應本身的單一職責,進行解耦,比方說:HOC,render props 模式。

繼承耦合度高,在React組件模式下更難用好,這是一個顯而易見的問題。

組合各個組件是分離的,因此組合更加符合單一責任原則,而且組合的狀況可以更好地利用好children, state, props。

說到底,繼承是一種多態工具,而不是一種代碼複用工具,當使用組合來實現代碼複用的時候,是不會產生繼承關係的。過分使用繼承的話,若是修改了父類,會損壞全部的子類。這是由於子類和父類的緊耦合關係是在編譯期產生的。

Mixins

var SetIntervalMixin = {
  componentWillMount: function() {
    this.intervals = [];
  },
  setInterval: function() {
    this.intervals.push(setInterval.apply(null, arguments));
  },
  componentWillUnmount: function() {
    this.intervals.forEach(clearInterval);
  }
};


var TickTock = createReactClass({
  mixins: [SetIntervalMixin], // Use the mixin
  getInitialState: function() {
    return {seconds: 0};
  },
  componentDidMount: function() {
    this.setInterval(this.tick, 1000); // Call a method on the mixin
  },
  tick: function() {
    this.setState({seconds: this.state.seconds + 1});
  },
  render: function() {
    return (
      <p>
        React has been running for {this.state.seconds} seconds.
      </p>
    );
  }
});
複製代碼
  • Mixins核心源碼基本上和autoBind的機制差很少,不過多贅述了。
  • Mixins的問題其實跟class extends有些類似的問題
  1. mixins形成了隱式的依賴 假設,你有一個組件有一個狀態count = 1 ,而後有一個同事建立了一個mixins,是一個通用的mixins,去讀取了本地狀態裏面的count 處理一些複用邏輯,過了幾個月以後,你但願作一些狀態共享的業務,把count也傳遞給別人,那麼作了狀態提高,把count拉高到父組件,這時候這個mixins就會爆炸了。而且mixins之間能夠相互依賴,移除其中一個,有可能會形成另一個的爆炸。在這種狀況下,很難準確的描述mixins之間的依賴關係

  2. mixins會有命名衝突的問題

  3. 相似於class 繼承的問題

因此綜合以上的種種問題,在React裏面,甚至是facebook這麼多優秀工程師的團隊,都會出現以上的問題,而且沒法很好的組織,重構,維護諸如此類的複雜問題。那明顯,mixins是一個bad design 。

因此,對於邏輯複用,組件複用,隨着時間的推移,經驗的推動,就出現了咱們最熟悉的higher order component了。

Higher Order Component

完美的解決了以上的種種問題,對於邏輯複用,組件複用可以很好的處理,正是由於HOC是以組合的形式出現的。hoc就不須要過多的介紹了。可是hoc依然出現了一些問題:

  1. 冗餘的嵌套高階組件,比方說Reach Router裏面大量使用了hoc。一個最基本的路由顯示,就出現了7層的hoc。
  2. 多個hoc組合以後,相同的props命名沒法區分究竟是來自哪一個hoc 因此就出現了render props

Render Props

Render props 和 hoc都是解決一樣的事情的,就是邏輯複用,全部的hoc都可以經過render props 重寫,render props 剔除了全部上面hoc的問題,寫法會相對優雅一點,可是這是須要分場景的,我我的以爲並無說全部的邏輯複用的hoc都用render props去重寫。有一些場景,的確不必去用render props,比方說一些權限問題,套一個render props是真的麻煩。由於render props終究是一個jsx,不能從外部解決問題,而是在render函數內解決問題。

function isAuth(Component) {
  return class Auth extends React.Component {
    state = {
      auth: false
    }
    componentDidMount() {
      setTimeout(() => {
        this.setState({ auth: true})
      }, 300)
    }
    render() {
      return(
        <div>
          {this.state.auth ? <Component {...props} /> : '無權查看'}
        </div>
      )
    }
  }
}
@isAuth
class Demo extends React.Component {

}

// render props
class Auth extends React.Component {
  state = {
    auth: false
  }
  componentDidMount() {
    setTimeout(() => {
      this.setState({ auth: true})
    }, 300)
  }
  renderError = () => {
    return(
      <div>Error component</div>
    )
  }
  render() {
    return (this.props.children({
      auth: this.state.auth,
      renderError: this.render
    }))
  }
}


class RenderProps extends React.Component{
  render() {
    return(
      <div>
        <Auth>
          {({auth, renderError}) => {
            return <div>
              {auth ? Component : renderError}
            </div>
          }}
        </Auth>
      </div>
    )
  }
}
複製代碼

對於Hoc來講基本沒有對JSX的入侵性,只須要套一個decorator或者套一個函數返回組件 可是對於render props就必然對JSX 有一個入侵性,也就是說,不管如何,你都須要有一個合理的JSX結構來組織。

但其實,Render props依然還有一些問題,就是call backhell了,比方說,最底下的一個div須要用到多個createContext的props,那就須要寫成這樣。

class Demo extends React.Component {
  render() {
    return(
      <Auth>
        {({ auth, renderError }) => (
          <Game>
            {(props) => (
              <Dude>
                {(props) => (
                  <Shit>
                    {props => (
                      <div>213</div>
                    )}
                  </Shit>
                )}
              </Dude>
            )}
          </Game>
        )}
      </Auth>
    )
  }
}
複製代碼

Hooks api 除了基本上可以解決現階段全部的問題,還解決了一些額外的問題

  • 編譯後代碼量
  • 看起來更加FP一點(僅僅是看起來)
  • 下降智障代碼出錯率
  • 讓更多的組件易於測試

階段總結一下:

  • Mixins(0.14x<): 邏輯複用初代目,雖然解決了邏輯複用,可是本質和Class inheritance有相似問題(工程協做會形成困擾)

  • Class Inheritance:是一種多態工具,而不是一種代碼複用工具,(須要很是完整的OOP能力,但也無法解決耦合問題)

  • Higher Order Component(0.14x-15.6):邏輯代碼複用以組合的形式出現,顆粒度適中,具有完整的state , props形態,符合React核心的單一職責原則(可是會形成冗餘嵌套組件的問題)

  • Render Props(16.x):優雅地處理hoc剩餘問題,但依然可能會出現(Call BackHell)

  • Hooks (16.7 alpha):基本是現階段邏輯複用的解決方案

0.14.x – 16.6(2016年-201七、8年) 這個裏面出現了不少的api更新,Reconciler的架構不斷地改進,以及拆包,例如像ReactDom , React.CSSTransitionGroup,React.CreateClass,React.PropTypes等,包括開始加入fiber架構在以後的代碼增進,ComponentDidCatch,getDerivedStateFromProps,getSnapShotBeforeUpdate等等等等

16.x RoadMap

除了一些咱們所已經接觸到的還有如下兩個(這兩個細節其實均可以從官網和iceland dan的演講視頻中找到)

  • 16.8: concurrent mode
  • 16.9: suspense for Data fetching

最後總結一下

對於這麼多突飛猛進的框架和API,我本身我的來講,是以解決業務,解決問題的想法去保持熱情從而學習他們的。

  1. 在歷史中發掘真理,
  2. 在過程當中迭代方案,
  3. 在業務中嘗試它們
相關文章
相關標籤/搜索