React中的Component組件詳解
一 前言
在 React 世界里,一切皆組件,我們寫的 React 項目全部起源于組件。組件可以分為兩類,一類是類( Class )組件,一類是函數(shù)( Function )組件。
本章節(jié),我們將一起探討 React 中類組件和函數(shù)組件的定義,不同組件的通信方式,以及常規(guī)組件的強化方式,幫助你全方位認識 React 組件,從而對 React 的底層邏輯有進一步的理解。
二 什么是React組件?
想要理解 React 組件是什么?我們首先要來分析一下組件和常規(guī)的函數(shù)和類到底有什么本質的區(qū)別。
/* 類 */
class textClass {
sayHello=()=>console.log('hello, my name is alien')
}
/* 類組件 */
class Index extends React.Component{
state={ message:`hello ,world!` }
sayHello=()=> this.setState({ message : 'hello, my name is alien' })
render(){
return <div style={{ marginTop:'50px' }} onClick={ this.sayHello } > { this.state.message } </div>
}
}
/* 函數(shù) */
function textFun (){
return 'hello, world'
}
/* 函數(shù)組件 */
function FunComponent(){
const [ message , setMessage ] = useState('hello,world')
return <div onClick={ ()=> setMessage('hello, my name is alien') } >{ message }</div>
}我們從上面可以清楚地看到,組件本質上就是類和函數(shù),但是與常規(guī)的類和函數(shù)不同的是,組件承載了渲染視圖的 UI 和更新視圖的 setState 、 useState 等方法。React 在底層邏輯上會像正常實例化類和正常執(zhí)行函數(shù)那樣處理的組件。
因此,函數(shù)與類上的特性在 React 組件上同樣具有,比如原型鏈,繼承,靜態(tài)屬性等,所以不要把 React 組件和類與函數(shù)獨立開來。
接下來,我們一起著重看一下 React 對組件的處理流程。
對于類組件的執(zhí)行,是在react-reconciler/src/ReactFiberClassComponent.js中:
function constructClassInstance(
workInProgress, // 當前正在工作的 fiber 對象
ctor, // 我們的類組件
props // props
){
/* 實例化組件,得到組件實例 instance */
const instance = new ctor(props, context)
}對于函數(shù)組件的執(zhí)行,是在react-reconciler/src/ReactFiberHooks.js中
function renderWithHooks(
current, // 當前函數(shù)組件對應的 `fiber`, 初始化
workInProgress, // 當前正在工作的 fiber 對象
Component, // 我們函數(shù)組件
props, // 函數(shù)組件第一個參數(shù) props
secondArg, // 函數(shù)組件其他參數(shù)
nextRenderExpirationTime, //下次渲染過期時間
){
/* 執(zhí)行我們的函數(shù)組件,得到 return 返回的 React.element對象 */
let children = Component(props, secondArg);
}從中,找到了執(zhí)行類組件和函數(shù)組件的函數(shù)。那么為了搞清楚 React 底層是如何處理組件的,首先來看一下類和函數(shù)組件是什么時候被實例化和執(zhí)行的?
在 React 調和渲染 fiber 節(jié)點的時候,如果發(fā)現(xiàn) fiber tag 是 ClassComponent = 1,則按照類組件邏輯處理,如果是 FunctionComponent = 0 則按照函數(shù)組件邏輯處理。當然 React 也提供了一些內(nèi)置的組件,比如說 Suspense 、Profiler 等。
三 二種不同 React 組件
1 class類組件
類組件的定義
在 class 組件中,除了繼承 React.Component ,底層還加入了 updater 對象,組件中調用的 setState 和 forceUpdate 本質上是調用了 updater 對象上的 enqueueSetState 和 enqueueForceUpdate 方法。
那么,React 底層是如何定義類組件的呢?
react/src/ReactBaseClasses.js
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 底層 React 的處理邏輯是,類組件執(zhí)行構造函數(shù)過程中會在實例上綁定 props 和 context ,初始化置空 refs 屬性,原型鏈上綁定setState、forceUpdate 方法。對于 updater,React 在實例化類組件之后會單獨綁定 update 對象。
|--------問與答---------|
問:如果沒有在 constructor 的 super 函數(shù)中傳遞 props,那么接下來 constructor 執(zhí)行上下文中就獲取不到 props ,這是為什么呢?
/* 假設我們在 constructor 中這么寫 */
constructor(){
super()
console.log(this.props) // 打印 undefined 為什么?
}答案很簡單,剛才的 Component 源碼已經(jīng)說得明明白白了,綁定 props 是在父類 Component 構造函數(shù)中,執(zhí)行 super 等于執(zhí)行 Component 函數(shù),此時 props 沒有作為第一個參數(shù)傳給 super() ,在 Component 中就會找不到 props 參數(shù),從而變成 undefined ,在接下來 constructor 代碼中打印 props 為 undefined 。
/* 解決問題 */
constructor(props){
super(props)
}|---------end----------|
為了更好地使用 React 類組件,我們首先看一下類組件各個部分的功能:
class Index extends React.Component{
constructor(...arg){
super(...arg) /* 執(zhí)行 react 底層 Component 函數(shù) */
}
state = {} /* state */
static number = 1 /* 內(nèi)置靜態(tài)屬性 */
handleClick= () => console.log(111) /* 方法: 箭頭函數(shù)方法直接綁定在this實例上 */
componentDidMount(){ /* 生命周期 */
console.log(Index.number,Index.number1) // 打印 1 , 2
}
render(){ /* 渲染函數(shù) */
return <div style={{ marginTop:'50px' }} onClick={ this.handerClick } >hello,React!</div>
}
}
Index.number1 = 2 /* 外置靜態(tài)屬性 */
Index.prototype.handleClick = ()=> console.log(222) /* 方法: 綁定在 Index 原型鏈的 方法*/上面把類組件的主要組成部分都展示給大家了。針對 state ,生命周期等部分,后續(xù)會有專門的章節(jié)進行講解。
|--------問與答---------|
問:上述綁定了兩個 handleClick ,那么點擊 div 之后會打印什么呢?
答:結果是 111 。因為在 class 類內(nèi)部,箭頭函數(shù)是直接綁定在實例對象上的,而第二個 handleClick 是綁定在 prototype 原型鏈上的,它們的優(yōu)先級是:實例對象上方法屬性 > 原型鏈對象上方法屬性。
|---------end----------|
對于 pureComponent 會在 React 渲染優(yōu)化章節(jié),詳細探討。
2 函數(shù)組件
ReactV16.8 hooks 問世以來,對函數(shù)組件的功能加以強化,可以在 function 組件中,做類組件一切能做的事情,甚至完全取締類組件。函數(shù)組件的結構相比類組件就簡單多了,比如說,下面寫了一個常規(guī)的函數(shù)組件:
class Index extends React.Component{
constructor(...arg){
super(...arg) /* 執(zhí)行 react 底層 Component 函數(shù) */
}
state = {} /* state */
static number = 1 /* 內(nèi)置靜態(tài)屬性 */
handleClick= () => console.log(111) /* 方法: 箭頭函數(shù)方法直接綁定在this實例上 */
componentDidMount(){ /* 生命周期 */
console.log(Index.number,Index.number1) // 打印 1 , 2
}
render(){ /* 渲染函數(shù) */
return <div style={{ marginTop:'50px' }} onClick={ this.handerClick } >hello,React!</div>
}
}
Index.number1 = 2 /* 外置靜態(tài)屬性 */
Index.prototype.handleClick = ()=> console.log(222) /* 方法: 綁定在 Index 原型鏈的 方法*/注意:不要嘗試給函數(shù)組件 prototype 綁定屬性或方法,即使綁定了也沒有任何作用,因為通過上面源碼中 React 對函數(shù)組件的調用,是采用直接執(zhí)行函數(shù)的方式,而不是通過new的方式。
那么,函數(shù)組件和類組件本質的區(qū)別是什么呢?
對于類組件來說,底層只需要實例化一次,實例中保存了組件的 state 等狀態(tài)。對于每一次更新只需要調用 render 方法以及對應的生命周期就可以了。但是在函數(shù)組件中,每一次更新都是一次新的函數(shù)執(zhí)行,一次函數(shù)組件的更新,里面的變量會重新聲明。
為了能讓函數(shù)組件可以保存一些狀態(tài),執(zhí)行一些副作用鉤子,React Hooks 應運而生,它可以幫助記錄 React 中組件的狀態(tài),處理一些額外的副作用。
四 組件通信方式
React 一共有 5 種主流的通信方式:
- props 和 callback 方式
- ref 方式。
- React-redux 或 React-mobx 狀態(tài)管理方式。
- context 上下文方式。
- event bus 事件總線。
這里主要講一下第1種和第5種,其余的會在對應章節(jié)詳細解讀。
① props 和 callback 方式
props 和 callback 可以作為 React 組件最基本的通信方式,父組件可以通過 props 將信息傳遞給子組件,子組件可以通過執(zhí)行 props 中的回調函數(shù) callback 來觸發(fā)父組件的方法,實現(xiàn)父與子的消息通訊。
父組件 -> 通過自身 state 改變,重新渲染,傳遞 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事件總線
當然利用 eventBus 也可以實現(xiàn)組件通信,但是在 React 中并不提倡用這種方式,我還是更提倡用 props 方式通信。如果說非要用 eventBus,我覺得它更適合用 React 做基礎構建的小程序,比如 Taro。接下來將上述 demo 通過 eventBus 方式進行改造。
import { BusService } from './eventBus'
/* event Bus */
function Son(){
const [ fatherSay , setFatherSay ] = useState('')
React.useEffect(()=>{
BusService.on('fatherSay',(value)=>{ /* 事件綁定 , 給父組件綁定事件 */
setFatherSay(value)
})
return function(){ BusService.off('fatherSay') /* 解綁事件 */ }
},[])
return <div className='son' >
我是子組件
<div> 父組件對我說:{ fatherSay } </div>
<input placeholder="我對父組件說" onChange={ (e)=> BusService.emit('childSay',e.target.value) } />
</div>
}
/* 父組件 */
function Father(){
const [ childSay , setChildSay ] = useState('')
React.useEffect(()=>{ /* 事件綁定 , 給子組件綁定事件 */
BusService.on('childSay',(value)=>{
setChildSay(value)
})
return function(){ BusService.off('childSay') /* 解綁事件 */ }
},[])
return <div className="box father" >
我是父組件
<div> 子組件對我說:{ childSay } </div>
<input placeholder="我對子組件說" onChange={ (e)=> BusService.emit('fatherSay',e.target.value) } />
<Son />
</div>
}這樣做不僅達到了和使用 props 同樣的效果,還能跨層級,不會受到 React 父子組件層級的影響。但是為什么很多人都不推薦這種方式呢?因為它有一些致命缺點。
- 需要手動綁定和解綁。
- 對于小型項目還好,但是對于中大型項目,這種方式的組件通信,會造成牽一發(fā)動全身的影響,而且后期難以維護,組件之間的狀態(tài)也是未知的。
- 一定程度上違背了 React 數(shù)據(jù)流向原則。
五 組件的強化方式
①類組件繼承
對于類組件的強化,首先想到的是繼承方式,之前開發(fā)的開源項目 react-keepalive-router 就是通過繼承 React-Router 中的 Switch 和 Router ,來達到緩存頁面的功能的。因為 React 中類組件,有良好的繼承屬性,所以可以針對一些基礎組件,首先實現(xiàn)一部分基礎功能,再針對項目要求進行有方向的改造、強化、添加額外功能。
基礎組件:
/* 人類 */
class Person extends React.Component{
constructor(props){
super(props)
console.log('hello , i am person')
}
componentDidMount(){ console.log(1111) }
eat(){ /* 吃飯 */ }
sleep(){ /* 睡覺 */ }
ddd(){ console.log('打豆豆') /* 打豆豆 */ }
render(){
return <div>
大家好,我是一個person
</div>
}
}
/* 程序員 */
class Programmer extends Person{
constructor(props){
super(props)
console.log('hello , i am Programmer too')
}
componentDidMount(){ console.log(this) }
code(){ /* 敲代碼 */ }
render(){
return <div style={ { marginTop:'50px' } } >
{ super.render() } { /* 讓 Person 中的 render 執(zhí)行 */ }
我還是一個程序員! { /* 添加自己的內(nèi)容 */ }
</div>
}
}
export default Programmer效果:

我們從上面不難發(fā)現(xiàn)這個繼承增強效果很優(yōu)秀。它的優(yōu)勢如下:
- 可以控制父類 render,還可以添加一些其他的渲染內(nèi)容;
- 可以共享父類方法,還可以添加額外的方法和屬性。
但是也有值得注意的地方,就是 state 和生命周期會被繼承后的組件修改。像上述 demo 中,Person 組件中的 componentDidMount 生命周期將不會被執(zhí)行。
②函數(shù)組件自定義 Hooks
在自定義 hooks 章節(jié),會詳細介紹自定義 hooks 的原理和編寫。
③HOC高階組件
在 HOC 章節(jié),會詳細介紹高階組件 HOC 。
六 總結
從本章節(jié)學到了哪些知識:
- 知道了 React 組件本質——UI + update + 常規(guī)的類和函數(shù) = React 組件 ,以及 React 對組件的底層處理邏輯。
- 明白了函數(shù)組件和類組件的區(qū)別。
- 掌握組件通信方式。
- 掌握了組件強化方式。
下一章節(jié),我們將走進 React 狀態(tài)管理 state 的世界中,一起探討 State 的奧秘。
到此這篇關于React中的Component組件詳解的文章就介紹到這了,更多相關React Component組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
react配置webpack-bundle-analyzer項目優(yōu)化踩坑記錄
這篇文章主要介紹了react配置webpack-bundle-analyzer項目優(yōu)化踩坑記錄,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06
React Native自定義標題欄組件的實現(xiàn)方法
今天講一下如何實現(xiàn)自定義標題欄組件,我們都知道RN有一個優(yōu)點就是可以組件化,在需要使用該組件的地方直接引用并傳遞一些參數(shù)就可以了,這種方式確實提高了開發(fā)效率。對React Native自定義標題欄組件的實現(xiàn)方法感興趣的朋友參考下2017-01-01
React?Hook?Form?優(yōu)雅處理表單使用指南
這篇文章主要為大家介紹了React?Hook?Form?優(yōu)雅處理表單使用指南,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03
useEffect?返回函數(shù)執(zhí)行過程源碼解析
這篇文章主要為大家介紹了useEffect?返回函數(shù)執(zhí)行過程源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-04-04

