React发展历程中找到问题

栏目: 服务器 · 发布时间: 7年前

内容简介:为什么要写这个主题,是因为首先就是因为周会立了一个flag要说分享,但是我又不想去分享一些什么类库了,因为一些别的类库对于我们业务来说,并没有什么特别好的帮助,其次之前跟同事聊天里面,他问了我一个问题“你们是怎么做到对新库,新API保持这种持续学习的热情的”,我是这么回答他的“因为业务”,哈哈,大家会觉得我在扯淡,但其实里面是有道理的,一些新的api,新的库其实就是为了解决一些历史问题,当你在当前环境下无法解决一些复杂问题而头疼,用一些绕来绕去的方式解决的时候,发现来了一个新的模式去解决,你就会保持兴奋和

为什么要写这个主题,是因为首先就是因为周会立了一个flag要说分享,但是我又不想去分享一些什么类库了,因为一些别的类库对于我们业务来说,并没有什么特别好的帮助,其次之前跟同事聊天里面,他问了我一个问题“你们是怎么做到对新库,新API保持这种持续学习的热情的”,我是这么回答他的“因为业务”,哈哈,大家会觉得我在扯淡,但其实里面是有道理的,一些新的api,新的库其实就是为了解决一些历史问题,当你在当前环境下无法解决一些复杂问题而头疼,用一些绕来绕去的方式解决的时候,发现来了一个新的模式去解决,你就会保持兴奋和激动。那这次就带着思考的角度去讲React的编年史,也希望各位能够从中复习或者有些小伙伴从没接触过一些 react历史问题,也同样看看为什么新的React会产生这些新的API来帮助开发者们。

Earlier than 0.14.x (2015年)

早于0.14x 的时候,ES6还没有普及,所以大家创建一个React的类的时候,都是以函数调用的形式创建的,传入对应的键值对函数执行相应的lifeCycle, state, 和 props。

一个普通最基本,带有props 以及 state的组件是以这样的形式组织的

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版本是基本上没有差异性的

State

state的初始值是以initialState的函数调用返回值来创建的

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就是这样实现的。

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,这就称为同一类型的组件。

首先可以肯定的是,用继承来实现逻辑复用没有问题,但是有局限性

先看一下代码

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年-2017、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. 在业务中尝试它们

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Boolean Reasoning

Boolean Reasoning

Brown, Frank Markham / 2003-4 / $ 19.15

A systematic treatment of Boolean reasoning, this concise, newly revised edition combines the works of early logicians with recent investigations, including previously unpublished research results. Th......一起来看看 《Boolean Reasoning》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具