React中的Component組件詳解
一 前言
在 React 世界里,一切皆組件,我們寫的 React 項目全部起源于組件。組件可以分為兩類,一類是類( Class )組件,一類是函數(shù)( Function )組件。
本章節(jié),我們將一起探討 React 中類組件和函數(shù)組件的定義,不同組件的通信方式,以及常規(guī)組件的強化方式,幫助你全方位認(rèn)識 React 組件,從而對 React 的底層邏輯有進一步的理解。
二 什么是React組件?
想要理解 React 組件是什么?我們首先要來分析一下組件和常規(guī)的函數(shù)和類到底有什么本質(zhì)的區(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> }
我們從上面可以清楚地看到,組件本質(zhì)上就是類和函數(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, // 當(dāng)前正在工作的 fiber 對象 ctor, // 我們的類組件 props // props ){ /* 實例化組件,得到組件實例 instance */ const instance = new ctor(props, context) }
對于函數(shù)組件的執(zhí)行,是在react-reconciler/src/ReactFiberHooks.js中
function renderWithHooks( current, // 當(dāng)前函數(shù)組件對應(yīng)的 `fiber`, 初始化 workInProgress, // 當(dāng)前正在工作的 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 調(diào)和渲染 fiber 節(jié)點的時候,如果發(fā)現(xiàn) fiber tag 是 ClassComponent = 1,則按照類組件邏輯處理,如果是 FunctionComponent = 0 則按照函數(shù)組件邏輯處理。當(dāng)然 React 也提供了一些內(nèi)置的組件,比如說 Suspense 、Profiler 等。
三 二種不同 React 組件
1 class類組件
類組件的定義
在 class 組件中,除了繼承 React.Component ,底層還加入了 updater 對象,組件中調(diào)用的 setState 和 forceUpdate 本質(zhì)上是調(diào)用了 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í)行構(gòu)造函數(shù)過程中會在實例上綁定 props 和 context ,初始化置空 refs 屬性,原型鏈上綁定setState、forceUpdate 方法。對于 updater,React 在實例化類組件之后會單獨綁定 update 對象。
|--------問與答---------|
問:如果沒有在 constructor 的 super 函數(shù)中傳遞 props,那么接下來 constructor 執(zhí)行上下文中就獲取不到 props ,這是為什么呢?
/* 假設(shè)我們在 constructor 中這么寫 */ constructor(){ super() console.log(this.props) // 打印 undefined 為什么? }
答案很簡單,剛才的 Component 源碼已經(jīng)說得明明白白了,綁定 props 是在父類 Component 構(gòu)造函數(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 之后會打印什么呢?
答:結(jié)果是 111 。因為在 class 類內(nèi)部,箭頭函數(shù)是直接綁定在實例對象上的,而第二個 handleClick 是綁定在 prototype 原型鏈上的,它們的優(yōu)先級是:實例對象上方法屬性 > 原型鏈對象上方法屬性。
|---------end----------|
對于 pureComponent
會在 React 渲染優(yōu)化章節(jié),詳細(xì)探討。
2 函數(shù)組件
ReactV16.8 hooks 問世以來,對函數(shù)組件的功能加以強化,可以在 function 組件中,做類組件一切能做的事情,甚至完全取締類組件。函數(shù)組件的結(jié)構(gòu)相比類組件就簡單多了,比如說,下面寫了一個常規(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ù)組件的調(diào)用,是采用直接執(zhí)行函數(shù)的方式,而不是通過new的方式。
那么,函數(shù)組件和類組件本質(zhì)的區(qū)別是什么呢?
對于類組件來說,底層只需要實例化一次,實例中保存了組件的 state 等狀態(tài)。對于每一次更新只需要調(diào)用 render 方法以及對應(yīng)的生命周期就可以了。但是在函數(shù)組件中,每一次更新都是一次新的函數(shù)執(zhí)行,一次函數(shù)組件的更新,里面的變量會重新聲明。
為了能讓函數(shù)組件可以保存一些狀態(tài),執(zhí)行一些副作用鉤子,React Hooks 應(yīng)運而生,它可以幫助記錄 React 中組件的狀態(tài),處理一些額外的副作用。
四 組件通信方式
React 一共有 5 種主流的通信方式:
- props 和 callback 方式
- ref 方式。
- React-redux 或 React-mobx 狀態(tài)管理方式。
- context 上下文方式。
- event bus 事件總線。
這里主要講一下第1種和第5種,其余的會在對應(yīng)章節(jié)詳細(xì)解讀。
① props 和 callback 方式
props 和 callback 可以作為 React 組件最基本的通信方式,父組件可以通過 props 將信息傳遞給子組件,子組件可以通過執(zhí)行 props 中的回調(diào)函數(shù) callback 來觸發(fā)父組件的方法,實現(xiàn)父與子的消息通訊。
父組件 -> 通過自身 state 改變,重新渲染,傳遞 props -> 通知子組件
子組件 -> 通過調(diào)用父組件 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事件總線
當(dāng)然利用 eventBus 也可以實現(xiàn)組件通信,但是在 React 中并不提倡用這種方式,我還是更提倡用 props 方式通信。如果說非要用 eventBus,我覺得它更適合用 React 做基礎(chǔ)構(gòu)建的小程序,比如 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 中類組件,有良好的繼承屬性,所以可以針對一些基礎(chǔ)組件,首先實現(xiàn)一部分基礎(chǔ)功能,再針對項目要求進行有方向的改造、強化、添加額外功能。
基礎(chǔ)組件:
/* 人類 */ 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é),會詳細(xì)介紹自定義 hooks 的原理和編寫。
③HOC高階組件
在 HOC 章節(jié),會詳細(xì)介紹高階組件 HOC 。
六 總結(jié)
從本章節(jié)學(xué)到了哪些知識:
- 知道了 React 組件本質(zhì)——UI + update + 常規(guī)的類和函數(shù) = React 組件 ,以及 React 對組件的底層處理邏輯。
- 明白了函數(shù)組件和類組件的區(qū)別。
- 掌握組件通信方式。
- 掌握了組件強化方式。
下一章節(jié),我們將走進 React 狀態(tài)管理 state 的世界中,一起探討 State 的奧秘。
到此這篇關(guān)于React中的Component組件詳解的文章就介紹到這了,更多相關(guān)React Component組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
react配置webpack-bundle-analyzer項目優(yōu)化踩坑記錄
這篇文章主要介紹了react配置webpack-bundle-analyzer項目優(yōu)化踩坑記錄,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06React Native自定義標(biāo)題欄組件的實現(xiàn)方法
今天講一下如何實現(xiàn)自定義標(biāo)題欄組件,我們都知道RN有一個優(yōu)點就是可以組件化,在需要使用該組件的地方直接引用并傳遞一些參數(shù)就可以了,這種方式確實提高了開發(fā)效率。對React Native自定義標(biāo)題欄組件的實現(xiàn)方法感興趣的朋友參考下2017-01-01React?Hook?Form?優(yōu)雅處理表單使用指南
這篇文章主要為大家介紹了React?Hook?Form?優(yōu)雅處理表單使用指南,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03useEffect?返回函數(shù)執(zhí)行過程源碼解析
這篇文章主要為大家介紹了useEffect?返回函數(shù)執(zhí)行過程源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-04-04