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. 在业务中尝试它们
相关文章
相关标签/搜索