Component
组件可以分为两类,一类是类( Class )组件,一类是函数( Function )组件。
React对组件的处理流程:
-
类组件
function constructClassInstance( workInProgress, // 当前正在工作的 fiber 对象 ctor, // 我们的类组件 props // props ){ /* 实例化组件,得到组件实例 instance */ const instance = new ctor(props, context) } -
函数式组件
function renderWithHooks( current, // 当前函数组件对应的 `fiber`, 初始化 workInProgress, // 当前正在工作的 fiber 对象 Component, // 我们函数组件 props, // 函数组件第一个参数 props secondArg, // 函数组件其他参数 nextRenderExpirationTime, //下次渲染过期时间 ){ /* 执行我们的函数组件,得到 return 返回的 React.element对象 */ let children = Component(props, secondArg); }
那么React在渲染fiber节点的时候如何区别该去使用那个函数对这个节点去处理呢?这里就要用到JSX中提到的fiber tag,当是0是则表明是FunctionComponent,则按照函数组件的逻辑去处理,若tag=1时表明为ClassComponent,则按照类组件的逻辑去处理。
组件的定义:
-
类组件:
在类组件中,除了继承 React.Component ,底层还混入了 updater 对象,组件中调用的 setState 和 forceUpdate 本质上是调用了 updater 对象上的 enqueueSetState 和 enqueueForceUpdate 方法。下面是混入和定义的源码:
function Component(props, context, updater) { this.props = props; //绑定props this.context = context; //绑定context this.refs = emptyObject; //绑定ref this.updater = updater || ReactNoopUpdateQueue; //上面所属的updater 对象 } /* 绑定setState 方法 */ Component.prototype.setState = function(partialState, callback) { this.updater.enqueueSetState(this, partialState, callback, 'setState'); } /* 绑定forceupdate 方法 */ Component.prototype.forceUpdate = function(callback) { this.updater.enqueueForceUpdate(this, callback, 'forceUpdate'); }看源码不难看出Component底层的处理逻辑,绑定props、context,并且将refs置成空,在原型链上绑定setState、forceUpdate 方法。对于 updater,React 在实例化类组件之后会单独绑定 update 对象。
-
函数式组件
函数式组件相对来说就要简单很多,并且自从ReactV16.8 hooks降临,函数式组件的功能得到了一次史诗级的加强,基本能够完成类组件能够完成的操作。下面便是一个简单的函数式组件:
function Index(){ console.log(Index.number) // 打印 1 const [ message , setMessage ] = useState('hello,world') /* hooks */ return <div onClick={() => setMessage('let us learn React!') } > { message } </div> /* 返回值 作为渲染ui */ } Index.number = 1 /* 绑定静态属性 */相比较于类组件,函数式组件感觉内聚性相对来说更强,写法也相对来说更简单。当然在编写函数式组件时也需要注意一些问题:不要尝试给函数组件 prototype 绑定属性或方法,即使绑定了也没有任何作用,因为通过上面源码中 React 对函数组件的调用,是采用直接执行函数的方式,而不是通过new的方式。
类组件和函数式组件的本质区别是:
函数式组件一般来说只需要实例化一次即可,在实例中保存了其内部的一些状态,如state,待到需要更新时只需要执行相关的生命周期函数及render方法即可。但在函数式组件中每一次更新都是一次函数的新执行,里面的变量会被重新定义一遍,因此在hooks诞生之前函数式组件的功能相对来说并不是很完备。
既然聊到了组件,那么必不可少的肯定是组件间的相互通信了,常见的组件间通信方式有以下几种:
- props 和 callback 方式
- ref 方式。
- React-redux 或 React-mobx 状态管理方式。
- context 上下文方式。
- event bus 事件总线。
在这主要是1、5,其他方法在后续之中会陆续谈到。
-
props 和 callback 方式,相信在React的基础中应该都有学到过,简单来说就是父组件将一个回调函数通过props传给子组件,而子组件就能够通过这个回调函数修改一些数据通知父组件发生重新渲染,同样子组件也能够依赖父组件传过来的props,当其改变时也发生更新,这样便实现了组件之间的通信。
简单示例:
/* 子组件 */ function Son(props){ const { fatherSay , sayFather } = props return <div className='son' > 我是子组件 <div> 父组件对我说:{ fatherSay } </div> <input placeholder="我对父组件说" onChange={ (e)=>sayFather(e.target.value) } /> </div> } /* 父组件 */ function Father(){ const [ childSay , setChildSay ] = useState('') const [ fatherSay , setFatherSay ] = useState('') return <div className="box father" > 我是父组件 <div> 子组件对我说:{ childSay } </div> <input placeholder="我对子组件说" onChange={ (e)=>setFatherSay(e.target.value) } /> <Son fatherSay={fatherSay} sayFather={ setChildSay } /> </div> } -
event bus事件总线方式,这也是很熟悉的一种方式,那么直接跳过用法看最终的效果和产生的影响。
这样做不仅达到了和使用 props 同样的效果,还能跨层级,不会受到 React 父子组件层级的影响。但在很多场景下都不会选择用此方法,因为它存在很多的缺点:
1.需要手动的进行绑定和解绑,这给组件的编写添加了较大的工作量,并且很容易忘记解绑。
2.在大型项目中可能会产生很大的影响,后期也是难以维护的。
3.优点违背了React的单项数据流原则。
组件的增强
-
类组件
类组件也能和类一样进行继承操作,子组件能够调用父组件的render方法实现渲染,也能够添加子组件自己的render方法,其它增强和类的继承相似,能够共享到父组件的方法,也能够添加属于子组件自己的方法。
-
函数式组件
1.函数式组件自定义hooks
2.HOC高阶组件
这两种方法都会在后续中提到。
总结:
通过对这篇文章的学习,能够学习到:
- React 对组件的底层处理逻辑。
- 明白了函数组件和类组件的区别。
- 掌握组件通信方式。
- 掌握了组件强化方式。