不少同窗用react
開發的時候,真正用到的React
的api
少之又少,基本停留在Component
,React.memo
等層面,實際react
源碼中,暴露出來的方法並很多,只是咱們平時不多用。可是React
暴露出這麼多api
並不是沒有用,想要玩轉react
,就要明白這些API
到底是幹什麼的,應用場景是什麼,今天就讓咱們從react
到 react-dom
,一次性把react
生產環境的暴露api
複習個遍(涵蓋90%+)。html
咱們把react
,API
,分爲組件類,工具類,hooks
,再加上 react-dom
,一共四大方向,分別加以探討。前端
爲了能讓屏幕前的你,更理解api
,我是絞盡腦汁,本文的每個api
基本都會出一個demo
演示效果,彌補一下天書般的react
文檔😂😂😂,還有就是我對api
基本概念的理解。創做不易,但願屏幕前的你能給筆者賞個贊,以此鼓勵我繼續創做前端硬文。node
老規矩,咱們帶着疑問開始今天的閱讀(自測掌握程度)?react
react
暴露的api
有哪些,該如何使用?react
提供了哪些自測性能的手段?ref
既然不能用在函數組件中,那麼父組件如何控制函數子組件內的state
和方法?createElement
和cloneElement
有什麼區別,應用場景是什麼?react
內置的children
遍歷方法,和數組方法,有什麼區別?react
怎麼將子元素渲染到父元素以外的指定容器中?我相信讀完這篇文章,這些問題全都會迎刃而解?redux
組件類,詳細分的話有三種類,第一類說白了就是我平時用於繼承的基類組件Component
,PureComponent
,還有就是react
提供的內置的組件,好比Fragment
,StrictMode
,另外一部分就是高階組件forwardRef
,memo
等。設計模式
Component
是class
組件的根基。類組件一切始於Component
。對於React.Component
使用,咱們沒有什麼好講的。咱們這裏重點研究一下react
對Component
作了些什麼。前端工程化
react/src/ReactBaseClasses.js
api
function Component(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
複製代碼
這就是Component
函數,其中updater
對象上保存着更新組件的方法。數組
咱們聲明的類組件是何時以何種形式被實例化的呢?瀏覽器
react-reconciler/src/ReactFiberClassComponent.js
constructClassInstance
function constructClassInstance( workInProgress, ctor, props ){
const instance = new ctor(props, context);
instance.updater = {
isMounted,
enqueueSetState(){
/* setState 觸發這裏面的邏輯 */
},
enqueueReplaceState(){},
enqueueForceUpdate(){
/* forceUpdate 觸發這裏的邏輯 */
}
}
}
複製代碼
對於Component
, react
處理邏輯仍是很簡單的,實例化咱們類組件,而後賦值updater
對象,負責組件的更新。而後在組件各個階段,執行類組件的render
函數,和對應的生命週期函數就能夠了。
PureComponent
和 Component
用法,差很少同樣,惟一不一樣的是,純組件PureComponent
會淺比較,props
和state
是否相同,來決定是否從新渲染組件。因此通常用於性能調優,減小render次數。
什麼叫作淺比較,我這裏舉個列子:
class Index extends React.PureComponent{
constructor(props){
super(props)
this.state={
data:{
name:'alien',
age:28
}
}
}
handerClick= () =>{
const { data } = this.state
data.age++
this.setState({ data })
}
render(){
const { data } = this.state
return <div className="box" > <div className="show" > <div> 你的姓名是: { data.name } </div> <div> 年齡: { data.age }</div> <button onClick={ this.handerClick } >age++</button> </div> </div>
}
}
複製代碼
點擊按鈕,沒有任何反應,由於PureComponent
會比較兩次data
對象,都指向同一個data
,沒有發生改變,因此不更新視圖。
解決這個問題很簡單,只須要在handerClick
事件中這麼寫:
this.setState({ data:{...data} })
複製代碼
淺拷貝就能根本解決問題。
React.memo
和PureComponent
做用相似,能夠用做性能優化,React.memo
是高階組件,函數組件和類組件均可以使用, 和區別PureComponent
是 React.memo
只能對props
的狀況肯定是否渲染,而PureComponent
是針對props
和state
。
React.memo
接受兩個參數,第一個參數原始組件自己,第二個參數,能夠根據一次更新中props
是否相同決定原始組件是否從新渲染。是一個返回布爾值,true
證實組件無須從新渲染,false
證實組件須要從新渲染,這個和類組件中的shouldComponentUpdate()
正好相反 。
React.memo: 第二個參數 返回 true
組件不渲染 , 返回 false
組件從新渲染。 shouldComponentUpdate: 返回 true
組件渲染 , 返回 false
組件不渲染。
接下來咱們作一個場景,控制組件在僅此一個props
數字變量,必定範圍渲染。
例子🌰:
控制 props
中的 number
:
1 只有 number
更改,組件渲染。
2 只有 number
小於 5 ,組件渲染。
function TextMemo(props){
console.log('子組件渲染')
if(props)
return <div>hello,world</div>
}
const controlIsRender = (pre,next)=>{
if(pre.number === next.number ){ // number 不改變 ,不渲染組件
return true
}else if(pre.number !== next.number && next.number > 5 ) { // number 改變 ,但值大於5 , 不渲染組件
return true
}else { // 不然渲染組件
return false
}
}
const NewTexMemo = memo(TextMemo,controlIsRender)
class Index extends React.Component{
constructor(props){
super(props)
this.state={
number:1,
num:1
}
}
render(){
const { num , number } = this.state
return <div> <div> 改變num:當前值 { num } <button onClick={ ()=>this.setState({ num:num + 1 }) } >num++</button> <button onClick={ ()=>this.setState({ num:num - 1 }) } >num--</button> </div> <div> 改變number: 當前值 { number } <button onClick={ ()=>this.setState({ number:number + 1 }) } > number ++</button> <button onClick={ ()=>this.setState({ number:number - 1 }) } > number -- </button> </div> <NewTexMemo num={ num } number={number} /> </div>
}
}
複製代碼
效果:
完美達到了效果,React.memo
必定程度上,能夠等價於組件外部使用shouldComponentUpdate
,用於攔截新老props
,肯定組件是否更新。
官網對forwardRef
的概念和用法很籠統,也沒有給定一個具體的案例。不少同窗不知道 forwardRef
具體怎麼用,下面我結合具體例子給你們講解forwardRef
應用場景。
1 轉發引入Ref
這個場景實際很簡單,好比父組件想獲取孫組件,某一個dom
元素。這種隔代ref
獲取引用,就須要forwardRef
來助力。
function Son (props){
const { grandRef } = props
return <div> <div> i am alien </div> <span ref={grandRef} >這個是想要獲取元素</span> </div>
}
class Father extends React.Component{
constructor(props){
super(props)
}
render(){
return <div> <Son grandRef={this.props.grandRef} /> </div>
}
}
const NewFather = React.forwardRef((props,ref)=><Father grandRef={ref} {...props} /> )
class GrandFather extends React.Component{
constructor(props){
super(props)
}
node = null
componentDidMount(){
console.log(this.node)
}
render(){
return <div> <NewFather ref={(node)=> this.node = node } /> </div>
}
}
複製代碼
效果
react
不容許ref
經過props
傳遞,由於組件上已經有 ref
這個屬性,在組件調和過程當中,已經被特殊處理,forwardRef
出現就是解決這個問題,把ref
轉發到自定義的forwardRef
定義的屬性上,讓ref
,能夠經過props
傳遞。
2 高階組件轉發Ref
一文吃透hoc
文章中講到,因爲屬性代理的hoc
,被包裹一層,因此若是是類組件,是經過ref
拿不到原始組件的實例的,不過咱們能夠經過forWardRef
轉發ref
。
function HOC(Component){
class Wrap extends React.Component{
render(){
const { forwardedRef ,...otherprops } = this.props
return <Component ref={forwardedRef} {...otherprops} />
}
}
return React.forwardRef((props,ref)=> <Wrap forwardedRef={ref} {...props} /> )
}
class Index extends React.Component{
componentDidMount(){
console.log(666)
}
render(){
return <div>hello,world</div>
}
}
const HocIndex = HOC(Index,true)
export default ()=>{
const node = useRef(null)
useEffect(()=>{
/* 就能夠跨層級,捕獲到 Index 組件的實例了 */
console.log(node.current.componentDidMount)
},[])
return <div><HocIndex ref={node} /></div>
}
複製代碼
如上,解決了高階組件引入Ref
的問題。
React.lazy 和 Suspense 技術還不支持服務端渲染。若是你想要在使用服務端渲染的應用中使用,咱們推薦 Loadable Components 這個庫
React.lazy
和Suspense
配合一塊兒用,可以有動態加載組件的效果。React.lazy
接受一個函數,這個函數須要動態調用 import()
。它必須返回一個 Promise
,該 Promise
須要 resolve
一個 default export
的 React
組件。
咱們模擬一個動態加載的場景。
父組件
import Test from './comTest'
const LazyComponent = React.lazy(()=> new Promise((resolve)=>{
setTimeout(()=>{
resolve({
default: ()=> <Test />
})
},2000)
}))
class index extends React.Component{
render(){
return <div className="context_box" style={ { marginTop :'50px' } } > <React.Suspense fallback={ <div className="icon" ><SyncOutlined spin /></div> } > <LazyComponent /> </React.Suspense> </div>
}
}
複製代碼
咱們用setTimeout
來模擬import
異步引入效果。
Test
class Test extends React.Component{
constructor(props){
super(props)
}
componentDidMount(){
console.log('--componentDidMount--')
}
render(){
return <div> <img src={alien} className="alien" /> </div>
}
}
複製代碼
效果
何爲Suspense
, Suspense
讓組件「等待」某個異步操做,直到該異步操做結束便可渲染。
用於數據獲取的 Suspense
是一個新特性,你可使用 <Suspense>
以聲明的方式來「等待」任何內容,包括數據。本文重點介紹它在數據獲取的用例,它也能夠用於等待圖像、腳本或其餘異步的操做。
上面講到高階組件lazy
時候,已經用 lazy
+ Suspense
模式,構建了異步渲染組件。咱們看一下官網文檔中的案例:
const ProfilePage = React.lazy(() => import('./ProfilePage')); // 懶加載
<Suspense fallback={<Spinner />}> <ProfilePage /> </Suspense>
複製代碼
react
不容許一個組件返回多個節點元素,好比說以下狀況
render(){
return <li> 🍎🍎🍎 </li>
<li> 🍌🍌🍌 </li>
<li> 🍇🍇🍇 </li>
}
複製代碼
若是咱們想解決這個狀況,很簡單,只須要在外層套一個容器元素。
render(){
return <div> <li> 🍎🍎🍎 </li> <li> 🍌🍌🍌 </li> <li> 🍇🍇🍇 </li> </div>
}
複製代碼
可是咱們不指望,增長額外的dom
節點,因此react
提供Fragment
碎片概念,可以讓一個組件返回多個元素。 因此咱們能夠這麼寫
<React.Fragment>
<li> 🍎🍎🍎 </li>
<li> 🍌🍌🍌 </li>
<li> 🍇🍇🍇 </li>
</React.Fragment>
複製代碼
還能夠簡寫成:
<>
<li> 🍎🍎🍎 </li>
<li> 🍌🍌🍌 </li>
<li> 🍇🍇🍇 </li>
</>
複製代碼
和Fragment
區別是,Fragment
能夠支持key
屬性。<></>
不支持key
屬性。
舒適提示。咱們經過map
遍歷後的元素,react
底層會處理,默認在外部嵌套一個<Fragment>
。
好比:
{
[1,2,3].map(item=><span key={item.id} >{ item.name }</span>)
}
複製代碼
react
底層處理以後,等價於:
<Fragment>
<span></span>
<span></span>
<span></span>
</Fragment>
複製代碼
Profiler
這個api
通常用於開發階段,性能檢測,檢測一次react
組件渲染用時,性能開銷。
Profiler
須要兩個參數:
第一個參數:是 id
,用於表識惟一性的Profiler
。
第二個參數:onRender
回調函數,用於渲染完成,接受渲染參數。
實踐:
const index = () => {
const callback = (...arg) => console.log(arg)
return <div > <div > <Profiler id="root" onRender={ callback } > <Router > <Meuns/> <KeepaliveRouterSwitch withoutRoute > { renderRoutes(menusList) } </KeepaliveRouterSwitch> </Router> </Profiler> </div> </div>
}
複製代碼
結果
onRender
root
-> Profiler
樹的 id
。mount
-> mount
掛載 , update
渲染了。6.685000262223184
-> 更新 committed
花費的渲染時間。4.430000321008265
-> 渲染整顆子樹須要的時間689.7299999836832
-> 本次更新開始渲染的時間698.5799999674782
-> 本次更新committed 的時間set{}
-> 本次更新的 interactions
的集合儘管 Profiler 是一個輕量級組件,咱們依然應該在須要時纔去使用它。對一個應用來講,每添加一些都會給 CPU 和內存帶來一些負擔。
StrictMode
見名知意,嚴格模式,用於檢測react
項目中的潛在的問題,。與 Fragment
同樣, StrictMode
不會渲染任何可見的 UI
。它爲其後代元素觸發額外的檢查和警告。
嚴格模式檢查僅在開發模式下運行;它們不會影響生產構建。
StrictMode
目前有助於:
ref API
的警告findDOMNode
方法的警告context API
實踐:識別不安全的生命週期
對於不安全的生命週期,指的是UNSAFE_componentWillMount
,UNSAFE_componentWillReceiveProps
, UNSAFE_componentWillUpdate
外層開啓嚴格模式:
<React.StrictMode>
<Router > <Meuns/> <KeepaliveRouterSwitch withoutRoute > { renderRoutes(menusList) } </KeepaliveRouterSwitch> </Router>
</React.StrictMode>
複製代碼
咱們在內層組件中,使用不安全的生命週期:
class Index extends React.Component{
UNSAFE_componentWillReceiveProps(){
}
render(){
return <div className="box" />
}
}
複製代碼
效果:
接下來咱們一塊兒來探究一下react
工具類函數的用法。
一提到createElement
,就不禁得和JSX
聯繫一塊兒。咱們寫的jsx
,最終會被 babel
,用createElement
編譯成react
元素形式。我寫一個組件,咱們看一下會被編譯成什麼樣子,
若是咱們在render
裏面這麼寫:
render(){
return <div className="box" > <div className="item" >生命週期</div> <Text mes="hello,world" /> <React.Fragment> Flagment </React.Fragment> { /* */ } text文本 </div>
}
複製代碼
會被編譯成這樣:
render() {
return React.createElement("div", { className: "box" },
React.createElement("div", { className: "item" }, "\u751F\u547D\u5468\u671F"),
React.createElement(Text, { mes: "hello,world" }),
React.createElement(React.Fragment, null, " Flagment "),
"text\u6587\u672C");
}
複製代碼
固然咱們能夠不用jsx
模式,而是直接經過createElement
進行開發。
createElement
模型:
React.createElement(
type,
[props],
[...children]
)
複製代碼
createElement
參數:
**第一個參數:**若是是組件類型,會傳入組件,若是是dom
元素類型,傳入div
或者span
之類的字符串。
第二個參數::第二個參數爲一個對象,在dom
類型中爲屬性,在組件
類型中爲props。
其餘參數:,依次爲children
,根據順序排列。
createElement作了些什麼?
通過createElement
處理,最終會造成 $$typeof = Symbol(react.element)
對象。對象上保存了該react.element
的信息。
可能有的同窗還傻傻的分不清楚cloneElement
和createElement
區別和做用。
createElement
把咱們寫的jsx
,變成element
對象; 而cloneElement
的做用是以 element
元素爲樣板克隆並返回新的 React
元素。返回元素的 props
是將新的 props
與原始元素的 props
淺層合併後的結果。
那麼cloneElement
感受在咱們實際業務組件中,可能沒什麼用,可是在一些開源項目,或者是公共插槽組件中用處仍是蠻大的,好比說,咱們能夠在組件中,劫持children element
,而後經過cloneElement
克隆element
,混入props
。經典的案例就是 react-router
中的Swtich
組件,經過這種方式,來匹配惟一的 Route
並加以渲染。
咱們設置一個場景,在組件中,去劫持children
,而後給children
賦能一些額外的props
:
function FatherComponent({ children }){
const newChildren = React.cloneElement(children, { age: 18})
return <div> { newChildren } </div>
}
function SonComponent(props){
console.log(props)
return <div>hello,world</div>
}
class Index extends React.Component{
render(){
return <div className="box" > <FatherComponent> <SonComponent name="alien" /> </FatherComponent> </div>
}
}
複製代碼
打印:
完美達到了效果!
createContext
用於建立一個Context
對象,createContext
對象中,包括用於傳遞 Context
對象值 value
的Provider
,和接受value
變化訂閱的Consumer
。
const MyContext = React.createContext(defaultValue)
複製代碼
createContext
接受一個參數defaultValue
,若是Consumer
上一級一直沒有Provider
,則會應用defaultValue
做爲value
。只有當組件所處的樹中沒有匹配到 Provider
時,其 defaultValue
參數纔會生效。
咱們來模擬一個 Context.Provider
和Context.Consumer
的例子:
function ComponentB(){
/* 用 Consumer 訂閱, 來自 Provider 中 value 的改變 */
return <MyContext.Consumer> { (value) => <ComponentA {...value} /> } </MyContext.Consumer>
}
function ComponentA(props){
const { name , mes } = props
return <div> <div> 姓名: { name } </div> <div> 想對你們說: { mes } </div> </div>
}
function index(){
const [ value , ] = React.useState({
name:'alien',
mes:'let us learn React '
})
return <div style={{ marginTop:'50px' }} > <MyContext.Provider value={value} > <ComponentB /> </MyContext.Provider> </div>
}
複製代碼
打印結果:
Provider
和Consumer
的良好的特性,能夠作數據的存和取,Consumer
一方面傳遞value
,另外一方面能夠訂閱value
的改變。
Provider
還有一個特性能夠層層傳遞value
,這種特性在react-redux
中表現的淋漓盡致。
React.createFactory(type)
複製代碼
返回用於生成指定類型 React 元素的函數。類型參數既能夠是標籤名字符串(像是 'div
' 或 'span
'),也能夠是 React 組件 類型 ( class
組件或函數組件),或是 React fragment
類型。
使用:
const Text = React.createFactory(()=><div>hello,world</div>)
function Index(){
return <div style={{ marginTop:'50px' }} > <Text/> </div>
}
複製代碼
效果
報出警告,這個api
將要被廢棄,咱們這裏就很少講了,若是想要達到一樣的效果,請用React.createElement
createRef
能夠建立一個 ref
元素,附加在react
元素上。
用法:
class Index extends React.Component{
constructor(props){
super(props)
this.node = React.createRef()
}
componentDidMount(){
console.log(this.node)
}
render(){
return <div ref={this.node} > my name is alien </div>
}
}
複製代碼
我的以爲createRef
這個方法,很雞肋,咱們徹底能夠class
類組件中這麼寫,來捕獲ref
。
class Index extends React.Component{
node = null
componentDidMount(){
console.log(this.node)
}
render(){
return <div ref={(node)=> this.node } > my name is alien </div>
}
}
複製代碼
或者在function
組件中這麼寫:
function Index(){
const node = React.useRef(null)
useEffect(()=>{
console.log(node.current)
},[])
return <div ref={node} > my name is alien </div>
}
複製代碼
這個方法能夠用來檢測是否爲react element
元素,接受待驗證對象,返回true
或者false
。這個api可能對於業務組件的開發,做用不大,由於對於組件內部狀態,都是已知的,咱們根本就不須要去驗證,是不是react element
元素。 可是,對於一塊兒公共組件或是開源庫,isValidElement
就頗有做用了。
實踐
咱們作一個場景,驗證容器組件的全部子組件,過濾到非react element
類型。
沒有用isValidElement
驗證以前:
const Text = () => <div>hello,world</div>
class WarpComponent extends React.Component{
constructor(props){
super(props)
}
render(){
return this.props.children
}
}
function Index(){
return <div style={{ marginTop:'50px' }} > <WarpComponent> <Text/> <div> my name is alien </div> Let's learn react together! </WarpComponent> </div>
}
複製代碼
過濾以前的效果
咱們用isValidElement
進行react element
驗證:
class WarpComponent extends React.Component{
constructor(props){
super(props)
this.newChidren = this.props.children.filter(item => React.isValidElement(item) )
}
render(){
return this.newChidren
}
}
複製代碼
過濾以後效果
過濾掉了非react element
的 Let's learn react together!
。
接下來的五個api
都是和react.Chidren
相關的,咱們來分別介紹一下,咱們先來看看官網的描述,React.Children
提供了用於處理 this.props.children
不透明數據結構的實用方法。
有的同窗會問遍歷 children
用數組方法,map
,forEach
不就能夠了嗎? 請咱們注意一下不透明數據結構
,什麼叫作不透明結構?
咱們先看一下透明的結構:
class Text extends React.Component{
render(){
return <div>hello,world</div>
}
}
function WarpComponent(props){
console.log(props.children)
return props.children
}
function Index(){
return <div style={{ marginTop:'50px' }} > <WarpComponent> <Text/> <Text/> <Text/> <span>hello,world</span> </WarpComponent> </div>
}
複製代碼
打印
可是咱們把Index
結構改變一下:
function Index(){
return <div style={{ marginTop:'50px' }} > <WarpComponent> { new Array(3).fill(0).map(()=><Text/>) } <span>hello,world</span> </WarpComponent> </div>
}
複製代碼
打印
這個數據結構,咱們不能正常的遍歷了,即便遍歷也不能遍歷,每個子元素。此時就須要 react.Chidren
來幫忙了。
可是咱們把WarpComponent
組件用react.Chidren
處理children
:
function WarpComponent(props){
const newChildren = React.Children.map(props.children,(item)=>item)
console.log(newChildren)
return newChildren
}
複製代碼
此時就能正常遍歷了,達到了預期效果。
注意 若是 children
是一個 Fragment
對象,它將被視爲單一子節點的狀況處理,而不會被遍歷。
Children.forEach
和Children.map
用法相似,Children.map
能夠返回新的數組,Children.forEach
僅停留在遍歷階段。
咱們將上面的WarpComponent
方法,用Children.forEach
改一下。
function WarpComponent(props){
React.Children.forEach(props.children,(item)=>console.log(item))
return props.children
}
複製代碼
children
中的組件總數量,等同於經過 map
或 forEach
調用回調函數的次數。對於更復雜的結果,Children.count
能夠返回同一級別子組件的數量。
咱們仍是把上述例子進行改造:
function WarpComponent(props){
const childrenCount = React.Children.count(props.children)
console.log(childrenCount,'childrenCount')
return props.children
}
function Index(){
return <div style={{ marginTop:'50px' }} > <WarpComponent> { new Array(3).fill(0).map((item,index) => new Array(2).fill(1).map((item,index1)=><Text key={index+index1} />)) } <span>hello,world</span> </WarpComponent> </div>
}
複製代碼
效果:
Children.toArray
返回,props.children
扁平化後結果。
function WarpComponent(props){
const newChidrenArray = React.Children.toArray(props.children)
console.log(newChidrenArray,'newChidrenArray')
return newChidrenArray
}
function Index(){
return <div style={{ marginTop:'50px' }} > <WarpComponent> { new Array(3).fill(0).map((item,index)=>new Array(2).fill(1).map((item,index1)=><Text key={index+index1} />)) } <span>hello,world</span> </WarpComponent> </div>
}
複製代碼
效果:
newChidrenArray ,就是扁平化的數組結構。React.Children.toArray()
在拉平展開子節點列表時,更改 key
值以保留嵌套數組的語義。也就是說, toArray
會爲返回數組中的每一個 key
添加前綴,以使得每一個元素 key
的範圍都限定在此函數入參數組的對象內。
驗證 children
是否只有一個子節點(一個 React
元素),若是有則返回它,不然此方法會拋出錯誤。
不惟一
function WarpComponent(props){
console.log(React.Children.only(props.children))
return props.children
}
function Index(){
return <div style={{ marginTop:'50px' }} > <WarpComponent> { new Array(3).fill(0).map((item,index)=><Text key={index} />) } <span>hello,world</span> </WarpComponent> </div>
}
複製代碼
效果
惟一
function WarpComponent(props){
console.log(React.Children.only(props.children))
return props.children
}
function Index(){
return <div style={{ marginTop:'50px' }} > <WarpComponent> <Text/> </WarpComponent> </div>
}
複製代碼
效果
React.Children.only()
不接受 React.Children.map()
的返回值,由於它是一個數組而並非 React
元素。
對於react-hooks
,我已經寫了三部曲,介紹了react-hooks
使用,自定義hooks
,以及react-hooks
原理,感興趣的同窗能夠去看看,文章末尾有連接,對於經常使用的api
,我這裏參考了react-hooks
如何使用那篇文章。並作了相應精簡化和一些內容的補充。
useState
能夠彌補函數組件沒有state
的缺陷。useState
能夠接受一個初識值,也能夠是一個函數action
,action
返回值做爲新的state
。返回一個數組,第一個值爲state
讀取值,第二個值爲改變state
的dispatchAction
函數。
咱們看一個例子:
const DemoState = (props) => {
/* number爲此時state讀取值 ,setNumber爲派發更新的函數 */
let [number, setNumber] = useState(0) /* 0爲初始值 */
return (<div> <span>{ number }</span> <button onClick={ ()=> { setNumber(number+1) /* 寫法一 */ setNumber(number=>number + 1 ) /* 寫法二 */ console.log(number) /* 這裏的number是不可以即時改變的 */ } } >num++</button> </div>)
}
複製代碼
useEffect
能夠彌補函數組件沒有生命週期的缺點。咱們能夠在useEffect
第一個參數回調函數中,作一些請求數據,事件監聽等操做,第二個參數做爲dep
依賴項,當依賴項發生變化,從新執行第一個函數。
useEffect能夠用做數據交互。
/* 模擬數據交互 */
function getUserInfo(a){
return new Promise((resolve)=>{
setTimeout(()=>{
resolve({
name:a,
age:16,
})
},500)
})
}
const DemoEffect = ({ a }) => {
const [ userMessage , setUserMessage ] :any= useState({})
const div= useRef()
const [number, setNumber] = useState(0)
/* 模擬事件監聽處理函數 */
const handleResize =()=>{}
/* useEffect使用 ,這裏若是不加限制 ,會是函數重複執行,陷入死循環*/
useEffect(()=>{
/* 請求數據 */
getUserInfo(a).then(res=>{
setUserMessage(res)
})
/* 操做dom */
console.log(div.current) /* div */
/* 事件監聽等 */
window.addEventListener('resize', handleResize)
/* 只有當props->a和state->number改變的時候 ,useEffect反作用函數從新執行 ,若是此時數組爲空[],證實函數只有在初始化的時候執行一次至關於componentDidMount */
},[ a ,number ])
return (<div ref={div} > <span>{ userMessage.name }</span> <span>{ userMessage.age }</span> <div onClick={ ()=> setNumber(1) } >{ number }</div> </div>)
}
複製代碼
useEffect能夠用做事件監聽,還有一些基於dom
的操做。,別忘了在useEffect
第一個參數回調函數,返一個函數用於清除事件監聽等操做。
const DemoEffect = ({ a }) => {
/* 模擬事件監聽處理函數 */
const handleResize =()=>{}
useEffect(()=>{
/* 定時器 延時器等 */
const timer = setInterval(()=>console.log(666),1000)
/* 事件監聽 */
window.addEventListener('resize', handleResize)
/* 此函數用於清除反作用 */
return function(){
clearInterval(timer)
window.removeEventListener('resize', handleResize)
}
},[ a ])
return (<div > </div>)
}
複製代碼
useMemo
接受兩個參數,第一個參數是一個函數,返回值用於產生保存值。 第二個參數是一個數組,做爲dep
依賴項,數組裏面的依賴項發生變化,從新執行第一個函數,產生新的值。
應用場景: 1 緩存一些值,避免從新執行上下文
const number = useMemo(()=>{
/** ....大量的邏輯運算 **/
return number
},[ props.number ]) // 只有 props.number 改變的時候,從新計算number的值。
複製代碼
2 減小沒必要要的dom
循環
/* 用 useMemo包裹的list能夠限定當且僅當list改變的時候才更新此list,這樣就能夠避免selectList從新循環 */
{useMemo(() => (
<div>{ selectList.map((i, v) => ( <span className={style.listSpan} key={v} > {i.patentName} </span> ))} </div>
), [selectList])}
複製代碼
3 減小子組件渲染
/* 只有當props中,list列表改變的時候,子組件才渲染 */
const goodListChild = useMemo(()=> <GoodList list={ props.list } /> ,[ props.list ])
複製代碼
useMemo
和 useCallback
接收的參數都是同樣,都是在其依賴項發生變化後才執行,都是返回緩存的值,區別在於 useMemo
返回的是函數運行的結果, useCallback
返回的是函數。 返回的callback
能夠做爲props
回調函數傳遞給子組件。
/* 用react.memo */
const DemoChildren = React.memo((props)=>{
/* 只有初始化的時候打印了 子組件更新 */
console.log('子組件更新')
useEffect(()=>{
props.getInfo('子組件')
},[])
return <div>子組件</div>
})
const DemoUseCallback=({ id })=>{
const [number, setNumber] = useState(1)
/* 此時usecallback的第一參數 (sonName)=>{ console.log(sonName) } 通過處理賦值給 getInfo */
const getInfo = useCallback((sonName)=>{
console.log(sonName)
},[id])
return <div> {/* 點擊按鈕觸發父組件更新 ,可是子組件沒有更新 */} <button onClick={ ()=>setNumber(number+1) } >增長</button> <DemoChildren getInfo={getInfo} /> </div>
}
複製代碼
useRef
的做用:
dom
元素,或者class
組件實例 。react-hooks原理
文章中講過,建立useRef
時候,會建立一個原始對象,只要函數組件不被銷燬,原始對象就會一直存在,那麼咱們能夠利用這個特性,來經過useRef
保存一些數據。const DemoUseRef = ()=>{
const dom= useRef(null)
const handerSubmit = ()=>{
/* <div >表單組件</div> dom 節點 */
console.log(dom.current)
}
return <div> {/* ref 標記當前dom節點 */} <div ref={dom} >表單組件</div> <button onClick={()=>handerSubmit()} >提交</button> </div>
}
複製代碼
useEffect
執行順序: 組件更新掛載完成 -> 瀏覽器 dom
繪製完成 -> 執行 useEffect
回調。 useLayoutEffect
執行順序: 組件更新掛載完成 -> 執行 useLayoutEffect
回調-> 瀏覽器dom
繪製完成。
因此說 useLayoutEffect
代碼可能會阻塞瀏覽器的繪製 。咱們寫的 effect
和 useLayoutEffect
,react
在底層會被分別打上PassiveEffect
,HookLayout
,在commit
階段區分出,在什麼時機執行。
const DemoUseLayoutEffect = () => {
const target = useRef()
useLayoutEffect(() => {
/*咱們須要在dom繪製以前,移動dom到制定位置*/
const { x ,y } = getPositon() /* 獲取要移動的 x,y座標 */
animate(target.current,{ x,y })
}, []);
return (
<div > <span ref={ target } className="animate"></span> </div>
)
}
複製代碼
在react-hooks
原理那篇文章中講解到,useState
底層就是一個簡單版的useReducer
useReducer
接受的第一個參數是一個函數,咱們能夠認爲它就是一個 reducer
, reducer
的參數就是常規 reducer
裏面的 state
和 action
,返回改變後的 state
, useReducer
第二個參數爲 state
的初始值 返回一個數組,數組的第一項就是更新以後 state
的值 ,第二個參數是派發更新的 dispatch
函數。
咱們來看一下useReducer
如何使用:
const DemoUseReducer = ()=>{
/* number爲更新後的state值, dispatchNumbner 爲當前的派發函數 */
const [ number , dispatchNumbner ] = useReducer((state,action)=>{
const { payload , name } = action
/* return的值爲新的state */
switch(name){
case 'add':
return state + 1
case 'sub':
return state - 1
case 'reset':
return payload
}
return state
},0)
return <div> 當前值:{ number } { /* 派發更新 */ } <button onClick={()=>dispatchNumbner({ name:'add' })} >增長</button> <button onClick={()=>dispatchNumbner({ name:'sub' })} >減小</button> <button onClick={()=>dispatchNumbner({ name:'reset' ,payload:666 })} >賦值</button> { /* 把dispatch 和 state 傳遞給子組件 */ } <MyChildren dispatch={ dispatchNumbner } State={{ number }} /> </div>
}
複製代碼
咱們可使用 useContext
,來獲取父級組件傳遞過來的 context
值,這個當前值就是最近的父級組件 Provider
設置的 value
值,useContext
參數通常是由 createContext
方式引入 ,也能夠父級上下文 context
傳遞 ( 參數爲 context
)。useContext
能夠代替 context.Consumer
來獲取 Provider
中保存的 value
值
/* 用useContext方式 */
const DemoContext = ()=> {
const value:any = useContext(Context)
/* my name is alien */
return <div> my name is { value.name }</div>
}
/* 用Context.Consumer 方式 */
const DemoContext1 = ()=>{
return <Context.Consumer> {/* my name is alien */} { (value)=> <div> my name is { value.name }</div> } </Context.Consumer>
}
export default ()=>{
return <div> <Context.Provider value={{ name:'alien' , age:18 }} > <DemoContext /> <DemoContext1 /> </Context.Provider> </div>
}
複製代碼
useImperativeHandle
能夠配合 forwardRef
自定義暴露給父組件的實例值。這個頗有用,咱們知道,對於子組件,若是是class
類組件,咱們能夠經過ref
獲取類組件的實例,可是在子組件是函數組件的狀況,若是咱們不能直接經過ref
的,那麼此時useImperativeHandle
和 forwardRef
配合就能達到效果。
useImperativeHandle
接受三個參數:
第一個參數ref: 接受 forWardRef
傳遞過來的 ref
。
第二個參數 createHandle
:處理函數,返回值做爲暴露給父組件的ref
對象。
第三個參數 deps
:依賴項 deps
,依賴項更改造成新的ref
對象。
咱們來模擬給場景,用useImperativeHandle
,使得父組件能讓子組件中的input
自動賦值並聚焦。
function Son (props,ref) {
console.log(props)
const inputRef = useRef(null)
const [ inputValue , setInputValue ] = useState('')
useImperativeHandle(ref,()=>{
const handleRefs = {
/* 聲明方法用於聚焦input框 */
onFocus(){
inputRef.current.focus()
},
/* 聲明方法用於改變input的值 */
onChangeValue(value){
setInputValue(value)
}
}
return handleRefs
},[])
return <div> <input placeholder="請輸入內容" ref={inputRef} value={inputValue} /> </div>
}
const ForwarSon = forwardRef(Son)
class Index extends React.Component{
inputRef = null
handerClick(){
const { onFocus , onChangeValue } =this.cur
onFocus()
onChangeValue('let us learn React!')
}
render(){
return <div style={{ marginTop:'50px' }} > <ForwarSon ref={node => (this.inputRef = node)} /> <button onClick={this.handerClick.bind(this)} >操控子組件</button> </div>
}
}
複製代碼
效果:
useDebugValue
可用於在 React
開發者工具中顯示自定義 hook
的標籤。這個hooks
目的就是檢查自定義hooks
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
// 在開發者工具中的這個 Hook 旁邊顯示標籤
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
複製代碼
咱們不推薦你向每一個自定義 Hook 添加 debug 值。當它做爲共享庫的一部分時才最有價值。在某些狀況下,格式化值的顯示多是一項開銷很大的操做。除非須要檢查 Hook,不然沒有必要這麼作。所以,useDebugValue 接受一個格式化函數做爲可選的第二個參數。該函數只有在 Hook 被檢查時纔會被調用。它接受 debug 值做爲參數,而且會返回一個格式化的顯示值。
useTransition
容許延時由state
改變而帶來的視圖渲染。避免沒必要要的渲染。它還容許組件將速度較慢的數據獲取更新推遲到隨後渲染,以便可以當即渲染更重要的更新。
const TIMEOUT_MS = { timeoutMs: 2000 }
const [startTransition, isPending] = useTransition(TIMEOUT_MS)
複製代碼
useTransition
接受一個對象, timeoutMs
代碼須要延時的時間。
返回一個數組。第一個參數: 是一個接受回調的函數。咱們用它來告訴 React
須要推遲的 state
。 第二個參數: 一個布爾值。表示是否正在等待,過分狀態的完成(延時state
的更新)。
下面咱們引入官網的列子,來了解useTransition
的使用。
const SUSPENSE_CONFIG = { timeoutMs: 2000 };
function App() {
const [resource, setResource] = useState(initialResource);
const [startTransition, isPending] = useTransition(SUSPENSE_CONFIG);
return (
<> <button disabled={isPending} onClick={() => { startTransition(() => { const nextUserId = getNextId(resource.userId); setResource(fetchProfileData(nextUserId)); }); }} > Next </button> {isPending ? " 加載中..." : null} <Suspense fallback={<Spinner />}> <ProfilePage resource={resource} /> </Suspense> </>
);
}
複製代碼
在這段代碼中,咱們使用 startTransition
包裝了咱們的數據獲取。這使咱們能夠當即開始獲取用戶資料的數據,同時推遲下一個用戶資料頁面以及其關聯的 Spinner
的渲染 2 秒鐘( timeoutMs
中顯示的時間)。
這個api
目前處於實驗階段,沒有被徹底開放出來。
接下來,咱們來一塊兒研究react-dom
中比較重要的api
。
render
是咱們最經常使用的react-dom
的 api
,用於渲染一個react
元素,通常react
項目咱們都用它,渲染根部容器app
。
ReactDOM.render(element, container[, callback])
複製代碼
使用
ReactDOM.render(
< App / >,
document.getElementById('app')
)
複製代碼
ReactDOM.render
會控制container
容器節點裏的內容,可是不會修改容器節點自己。
服務端渲染用hydrate
。用法與 render()
相同,但它用於在 ReactDOMServer
渲染的容器中對 HTML
的內容進行 hydrate
操做。
ReactDOM.hydrate(element, container[, callback])
複製代碼
Portal
提供了一種將子節點渲染到存在於父組件之外的 DOM
節點的優秀的方案。createPortal
能夠把當前組件或 element
元素的子節點,渲染到組件以外的其餘地方。
那麼具體應用到什麼場景呢?
好比一些全局的彈窗組件model
,<Model/>
組件通常都寫在咱們的組件內部,卻是真正掛載的dom
,都是在外層容器,好比body
上。此時就很適合createPortal
API。
createPortal
接受兩個參數:
ReactDOM.createPortal(child, container)
複製代碼
第一個: child
是任何可渲染的 React
子元素 第二個: container
是一個 DOM
元素。
接下來咱們實踐一下:
function WrapComponent({ children }){
const domRef = useRef(null)
const [ PortalComponent, setPortalComponent ] = useState(null)
React.useEffect(()=>{
setPortalComponent( ReactDOM.createPortal(children,domRef.current) )
},[])
return <div> <div className="container" ref={ domRef } ></div> { PortalComponent } </div>
}
class Index extends React.Component{
render(){
return <div style={{ marginTop:'50px' }} > <WrapComponent> <div >hello,world</div> </WrapComponent> </div>
}
}
複製代碼
效果
咱們能夠看到,咱們children
實際在container
以外掛載的,可是已經被createPortal
渲染到container
中。
在react-legacy
模式下,對於事件,react
事件有批量更新來處理功能,可是這一些很是規的事件中,批量更新功能會被打破。因此咱們能夠用react-dom
中提供的unstable_batchedUpdates
來進行批量更新。
一次點擊實現的批量更新
class Index extends React.Component{
constructor(props){
super(props)
this.state={
numer:1,
}
}
handerClick=()=>{
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
}
render(){
return <div style={{ marginTop:'50px' }} > <button onClick={ this.handerClick } >click me</button> </div>
}
}
複製代碼
效果
渲染次數一次。
批量更新條件被打破
handerClick=()=>{
Promise.resolve().then(()=>{
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
})
}
複製代碼
效果
渲染次數三次。
unstable_batchedUpdate助力
handerClick=()=>{
Promise.resolve().then(()=>{
ReactDOM.unstable_batchedUpdates(()=>{
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
})
})
}
複製代碼
渲染次數一次,完美解決批量更新問題。
flushSync
能夠將回調函數中的更新任務,放在一個較高的優先級中。咱們知道react
設定了不少不一樣優先級的更新任務。若是一次更新任務在flushSync
回調函數內部,那麼將得到一個較高優先級的更新。好比
ReactDOM.flushSync(()=>{
/* 這次更新將設置一個較高優先級的更新 */
this.setState({ name: 'alien' })
})
複製代碼
爲了讓你們理解flushSync
,我這裏作一個demo
奉上,
/* flushSync */
import ReactDOM from 'react-dom'
class Index extends React.Component{
state={ number:0 }
handerClick=()=>{
setTimeout(()=>{
this.setState({ number: 1 })
})
this.setState({ number: 2 })
ReactDOM.flushSync(()=>{
this.setState({ number: 3 })
})
this.setState({ number: 4 })
}
render(){
const { number } = this.state
console.log(number) // 打印什麼??
return <div> <div>{ number }</div> <button onClick={this.handerClick} >測試flushSync</button> </div>
}
}
複製代碼
先不看答案,點擊一下按鈕,打印什麼呢?
咱們來點擊一下看看
打印 0 3 4 1 ,相信不難理解爲何這麼打印了。
flushSync
this.setState({ number: 3 })
設定了一個高優先級的更新,因此3 先被打印相信這個demo
讓咱們更深刻了解了flushSync
。
findDOMNode
用於訪問組件DOM
元素節點,react
推薦使用ref
模式,不指望使用findDOMNode
。
ReactDOM.findDOMNode(component)
複製代碼
注意的是:
1 findDOMNode
只能用在已經掛載的組件上。
2 若是組件渲染內容爲 null
或者是 false
,那麼 findDOMNode
返回值也是 null
。
3 findDOMNode
不能用於函數組件。
接下來讓咱們看一下,findDOMNode
具體怎麼使用的:
class Index extends React.Component{
handerFindDom=()=>{
console.log(ReactDOM.findDOMNode(this))
}
render(){
return <div style={{ marginTop:'100px' }} > <div>hello,world</div> <button onClick={ this.handerFindDom } >獲取容器dom</button> </div>
}
}
複製代碼
效果:
咱們徹底能夠將外層容器用ref
來標記,獲取捕獲原生的dom
節點。
從 DOM
中卸載組件,會將其事件處理器和 state
一併清除。 若是指定容器上沒有對應已掛載的組件,這個函數什麼也不會作。若是組件被移除將會返回 true
,若是沒有組件可被移除將會返回 false
。
咱們來簡單舉例看看unmountComponentAtNode
如何使用?
function Text(){
return <div>hello,world</div>
}
class Index extends React.Component{
node = null
constructor(props){
super(props)
this.state={
numer:1,
}
}
componentDidMount(){
/* 組件初始化的時候,建立一個 container 容器 */
ReactDOM.render(<Text/> , this.node )
}
handerClick=()=>{
/* 點擊卸載容器 */
const state = ReactDOM.unmountComponentAtNode(this.node)
console.log(state)
}
render(){
return <div style={{ marginTop:'50px' }} > <div ref={ ( node ) => this.node = node } ></div> <button onClick={ this.handerClick } >click me</button> </div>
}
}
複製代碼
效果
本文經過react
組件層面,工具層面,hooks
層面,react-dom
瞭解了api
的用法,但願看完的同窗,可以對着文章中的demo
本身敲一遍,到頭來會發現本身成長很多。
最後, 送人玫瑰,手留餘香,以爲有收穫的朋友能夠給筆者點贊,關注一波 ,陸續更新前端超硬核文章。
提早透漏:接下來會出一部揭祕react
事件系統的文章。
感興趣的同窗請關注公衆號 前端Sharing
持續推送優質好文
文章中,對於其餘沒有講到的react-hooks
,建議你們看react-hooks
三部曲。
react-hooks三部曲
第一部: react-hooks如何使用 150+
贊👍
react進階系列
「react進階」年終送給react開發者的八條優化建議 918+
贊👍
「react進階」一文吃透React高階組件(HOC) 330+
贊👍
react源碼系列
開源項目系列