内容简介:为什么要写这个主题,是因为首先就是因为周会立了一个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有些相似的问题
-
mixins造成了隐式的依赖 假设,你有一个组件有一个状态count = 1 ,然后有一个同事创建了一个mixins,是一个通用的mixins,去读取了本地状态里面的count 处理一些复用逻辑,过了几个月之后,你希望做一些状态共享的业务,把count也传递给别人,那么做了状态提升,把count拉高到父组件,这时候这个mixins就会爆炸了。并且mixins之间可以相互依赖,移除其中一个,有可能会造成另外一个的爆炸。在这种情况下,很难准确的描述mixins之间的依赖关系
-
mixins会有命名冲突的问题
-
类似于class 继承的问题
所以综合以上的种种问题,在React里面,甚至是facebook这么多优秀工程师的团队,都会出现以上的问题,并且无法很好的组织,重构,维护诸如此类的复杂问题。那明显,mixins是一个bad design 。
所以,对于逻辑复用,组件复用,随着时间的推移,经验的推进,就出现了我们最熟悉的 higher order component
了。
Higher Order Component
完美的解决了以上的种种问题,对于逻辑复用,组件复用能够很好的处理,正是因为HOC是以组合的形式出现的。hoc就不需要过多的介绍了。但是hoc依然出现了一些问题:
- 冗余的嵌套高阶组件,比方说Reach Router里面大量使用了hoc。一个最基本的路由显示,就出现了7层的hoc。
- 多个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,我自己个人来说,是以解决业务,解决问题的想法去保持热情从而学习他们的。
- 在历史中发掘真理,
- 在过程中迭代方案,
- 在业务中尝试它们
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
TED Talks Storytelling: 23 Storytelling Techniques from the Best
Akash Karia / CreateSpace Independent Publishing Platform / 2015-1-11 / USD 6.99
"Every speaker can put these ideas into practice immediately -- and they should!" ~ Dr. Richard C. Harris, Certified World Class Speaking Coach "An insightful read" ~Dennis Waller, Top 500 Revie......一起来看看 《TED Talks Storytelling: 23 Storytelling Techniques from the Best》 这本书的介绍吧!