「React進階」 React所有api解讀+基礎實踐大全(夯實基礎2萬字總結)

不少同窗用react開發的時候,真正用到的Reactapi少之又少,基本停留在Component,React.memo等層面,實際react源碼中,暴露出來的方法並很多,只是咱們平時不多用。可是React暴露出這麼多api並不是沒有用,想要玩轉react,就要明白這些API到底是幹什麼的,應用場景是什麼,今天就讓咱們從reactreact-dom,一次性把react生產環境的暴露api複習個遍(涵蓋90%+)。html

咱們把react,API,分爲組件類工具類hooks,再加上 react-dom ,一共四大方向,分別加以探討。前端

爲了能讓屏幕前的你,更理解api,我是絞盡腦汁,本文的每個api基本都會出一個demo演示效果,彌補一下天書般的react文檔😂😂😂,還有就是我對api基本概念的理解。創做不易,但願屏幕前的你能給筆者賞個,以此鼓勵我繼續創做前端硬文。node

老規矩,咱們帶着疑問開始今天的閱讀(自測掌握程度)?react

  • 1 react暴露的api有哪些,該如何使用?
  • 2 react提供了哪些自測性能的手段?
  • 3 ref既然不能用在函數組件中,那麼父組件如何控制函數子組件內的state和方法?
  • 4 createElementcloneElement有什麼區別,應用場景是什麼?
  • 5 react內置的children遍歷方法,和數組方法,有什麼區別?
  • 6 react怎麼將子元素渲染到父元素以外的指定容器中?
  • ...

我相信讀完這篇文章,這些問題全都會迎刃而解?redux

組件類

組件類,詳細分的話有三種類,第一類說白了就是我平時用於繼承的基類組件Component,PureComponent,還有就是react提供的內置的組件,好比Fragment,StrictMode,另外一部分就是高階組件forwardRef,memo等。設計模式

comp.jpg

Component

Componentclass組件的根基。類組件一切始於Component。對於React.Component使用,咱們沒有什麼好講的。咱們這裏重點研究一下reactComponent作了些什麼。前端工程化

react/src/ReactBaseClasses.jsapi

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 觸發這裏的邏輯 */
        }
    }
}
複製代碼

對於Componentreact 處理邏輯仍是很簡單的,實例化咱們類組件,而後賦值updater對象,負責組件的更新。而後在組件各個階段,執行類組件的render函數,和對應的生命週期函數就能夠了。

PureComponent

PureComponentComponent用法,差很少同樣,惟一不一樣的是,純組件PureComponent會淺比較,propsstate是否相同,來決定是否從新渲染組件。因此通常用於性能調優,減小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.gif 點擊按鈕,沒有任何反應,由於PureComponent會比較兩次data對象,都指向同一個data,沒有發生改變,因此不更新視圖。

解決這個問題很簡單,只須要在handerClick事件中這麼寫:

this.setState({ data:{...data} })
複製代碼

淺拷貝就能根本解決問題。

memo

React.memoPureComponent做用相似,能夠用做性能優化,React.memo 是高階組件,函數組件和類組件均可以使用, 和區別PureComponentReact.memo只能對props的狀況肯定是否渲染,而PureComponent是針對propsstate

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>
    }
}
複製代碼

效果:

memo.gif

完美達到了效果,React.memo必定程度上,能夠等價於組件外部使用shouldComponentUpdate ,用於攔截新老props,肯定組件是否更新。

forwardRef

官網對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>
    }
}
複製代碼

效果

forwaedRef.jpg

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的問題。

lazy

React.lazy 和 Suspense 技術還不支持服務端渲染。若是你想要在使用服務端渲染的應用中使用,咱們推薦 Loadable Components 這個庫

React.lazySuspense配合一塊兒用,可以有動態加載組件的效果。React.lazy 接受一個函數,這個函數須要動態調用 import()。它必須返回一個 Promise ,該 Promise 須要 resolve 一個 default exportReact 組件。

咱們模擬一個動態加載的場景。

父組件

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>
    }
}
複製代碼

效果

lazy.gif

Suspense

何爲Suspense, Suspense 讓組件「等待」某個異步操做,直到該異步操做結束便可渲染。

用於數據獲取的 Suspense 是一個新特性,你可使用 <Suspense> 以聲明的方式來「等待」任何內容,包括數據。本文重點介紹它在數據獲取的用例,它也能夠用於等待圖像、腳本或其餘異步的操做。

上面講到高階組件lazy時候,已經用 lazy + Suspense模式,構建了異步渲染組件。咱們看一下官網文檔中的案例:

const ProfilePage = React.lazy(() => import('./ProfilePage')); // 懶加載
<Suspense fallback={<Spinner />}> <ProfilePage /> </Suspense>
複製代碼

Fragment

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

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>
}
複製代碼

結果

Profiler.jpg

onRender

  • 0 -id: root -> Profiler 樹的 id
  • 1 -phase: mount -> mount 掛載 , update 渲染了。
  • 2 -actualDuration: 6.685000262223184 -> 更新 committed 花費的渲染時間。
  • 3 -baseDuration: 4.430000321008265 -> 渲染整顆子樹須要的時間
  • 4 -startTime : 689.7299999836832 -> 本次更新開始渲染的時間
  • 5 -commitTime : 698.5799999674782 -> 本次更新committed 的時間
  • 6 -interactions: set{} -> 本次更新的 interactions 的集合

儘管 Profiler 是一個輕量級組件,咱們依然應該在須要時纔去使用它。對一個應用來講,每添加一些都會給 CPU 和內存帶來一些負擔。

StrictMode

StrictMode見名知意,嚴格模式,用於檢測react項目中的潛在的問題,。與 Fragment 同樣, StrictMode 不會渲染任何可見的 UI 。它爲其後代元素觸發額外的檢查和警告。

嚴格模式檢查僅在開發模式下運行;它們不會影響生產構建。

StrictMode目前有助於:

  • ①識別不安全的生命週期。
  • ②關於使用過期字符串 ref API 的警告
  • ③關於使用廢棄的 findDOMNode 方法的警告
  • ④檢測意外的反作用
  • ⑤檢測過期的 context API

實踐:識別不安全的生命週期

對於不安全的生命週期,指的是UNSAFE_componentWillMountUNSAFE_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" />   
    }
}
複製代碼

效果:

strictMode.jpg

工具類

接下來咱們一塊兒來探究一下react工具類函數的用法。

utils.jpg

createElement

一提到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

可能有的同窗還傻傻的分不清楚cloneElementcreateElement區別和做用。

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>   
    }
}
複製代碼

打印:

cloneElment.jpg

完美達到了效果!

createContext

createContext用於建立一個Context對象,createContext對象中,包括用於傳遞 Context 對象值 valueProvider,和接受value變化訂閱的Consumer

const MyContext = React.createContext(defaultValue)
複製代碼

createContext接受一個參數defaultValue,若是Consumer上一級一直沒有Provider,則會應用defaultValue做爲value只有當組件所處的樹中沒有匹配到 Provider 時,其 defaultValue 參數纔會生效。

咱們來模擬一個 Context.ProviderContext.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>
}
複製代碼

打印結果:

createContent.jpg

ProviderConsumer的良好的特性,能夠作數據的Consumer一方面傳遞value,另外一方面能夠訂閱value的改變。

Provider還有一個特性能夠層層傳遞value,這種特性在react-redux中表現的淋漓盡致。

createFactory

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>
}
複製代碼

效果

createFactory.jpg

報出警告,這個api將要被廢棄,咱們這裏就很少講了,若是想要達到一樣的效果,請用React.createElement

createRef

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>
}
複製代碼

isValidElement

這個方法能夠用來檢測是否爲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.jpg

咱們用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
    }
}
複製代碼

過濾以後效果

isValidElement111.jpg

過濾掉了非react elementLet's learn react together!

Children.map

接下來的五個api都是和react.Chidren相關的,咱們來分別介紹一下,咱們先來看看官網的描述,React.Children 提供了用於處理 this.props.children 不透明數據結構的實用方法。

有的同窗會問遍歷 children用數組方法,mapforEach 不就能夠了嗎? 請咱們注意一下不透明數據結構,什麼叫作不透明結構?

咱們先看一下透明的結構:

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>
}
複製代碼

打印

chidrenmap.jpg

可是咱們把Index結構改變一下:

function Index(){
    return <div style={{ marginTop:'50px' }} > <WarpComponent> { new Array(3).fill(0).map(()=><Text/>) } <span>hello,world</span> </WarpComponent> </div>
}
複製代碼

打印

chidrenmap2.jpg

這個數據結構,咱們不能正常的遍歷了,即便遍歷也不能遍歷,每個子元素。此時就須要 react.Chidren 來幫忙了。

可是咱們把WarpComponent組件用react.Chidren處理children:

function WarpComponent(props){
    const newChildren = React.Children.map(props.children,(item)=>item)
    console.log(newChildren)
    return newChildren
} 
複製代碼

此時就能正常遍歷了,達到了預期效果。

C71364B2-25E8-4F7D-A26D-50CA36AF4E33.jpg

注意 若是 children 是一個 Fragment 對象,它將被視爲單一子節點的狀況處理,而不會被遍歷。

Children.forEach

Children.forEachChildren.map 用法相似,Children.map能夠返回新的數組,Children.forEach僅停留在遍歷階段。

咱們將上面的WarpComponent方法,用Children.forEach改一下。

function WarpComponent(props){
    React.Children.forEach(props.children,(item)=>console.log(item))
    return props.children
}   
複製代碼

Children.count

children 中的組件總數量,等同於經過 mapforEach 調用回調函數的次數。對於更復雜的結果,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>
}
複製代碼

效果:

chidrencunt.jpg

Children.toArray

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>
}
複製代碼

效果:

chuldeanarrgy.jpg

newChidrenArray ,就是扁平化的數組結構。React.Children.toArray() 在拉平展開子節點列表時,更改 key 值以保留嵌套數組的語義。也就是說, toArray 會爲返回數組中的每一個 key 添加前綴,以使得每一個元素 key 的範圍都限定在此函數入參數組的對象內。

Children.only

驗證 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>
}
複製代碼

效果

falseonly.jpg

惟一

function WarpComponent(props){
    console.log(React.Children.only(props.children))
    return props.children
}   
function Index(){
    return <div style={{ marginTop:'50px' }} > <WarpComponent> <Text/> </WarpComponent> </div>
}
複製代碼

效果

only.jpg

React.Children.only() 不接受 React.Children.map() 的返回值,由於它是一個數組而並非 React 元素。

react-hooks

對於react-hooks,我已經寫了三部曲,介紹了react-hooks使用,自定義hooks,以及react-hooks原理,感興趣的同窗能夠去看看,文章末尾有連接,對於經常使用的api,我這裏參考了react-hooks如何使用那篇文章。並作了相應精簡化和一些內容的補充。

hooks.jpg

useState

useState能夠彌補函數組件沒有state的缺陷。useState能夠接受一個初識值,也能夠是一個函數actionaction返回值做爲新的state。返回一個數組,第一個值爲state讀取值,第二個值爲改變statedispatchAction函數。

咱們看一個例子:

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能夠彌補函數組件沒有生命週期的缺點。咱們能夠在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

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 ])
複製代碼

useCallback

useMemouseCallback 接收的參數都是同樣,都是在其依賴項發生變化後才執行,都是返回緩存的值,區別在於 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

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>
}
複製代碼

useLayoutEffect

useEffect執行順序: 組件更新掛載完成 -> 瀏覽器 dom 繪製完成 -> 執行 useEffect 回調。 useLayoutEffect 執行順序: 組件更新掛載完成 -> 執行 useLayoutEffect 回調-> 瀏覽器dom繪製完成。

因此說 useLayoutEffect 代碼可能會阻塞瀏覽器的繪製 。咱們寫的 effectuseLayoutEffectreact在底層會被分別打上PassiveEffectHookLayout,在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>
    )
}
複製代碼

useReducer

react-hooks原理那篇文章中講解到,useState底層就是一個簡單版的useReducer

useReducer 接受的第一個參數是一個函數,咱們能夠認爲它就是一個 reducer , reducer 的參數就是常規 reducer 裏面的 stateaction ,返回改變後的 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

咱們可使用 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

useImperativeHandle 能夠配合 forwardRef 自定義暴露給父組件的實例值。這個頗有用,咱們知道,對於子組件,若是是class類組件,咱們能夠經過ref獲取類組件的實例,可是在子組件是函數組件的狀況,若是咱們不能直接經過ref的,那麼此時useImperativeHandleforwardRef配合就能達到效果。

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>
    }
}
複製代碼

效果:

useImperativeHandle.gif

useDebugValue

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

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

接下來,咱們來一塊兒研究react-dom中比較重要的api

react-dom.jpg

render

render 是咱們最經常使用的react-domapi,用於渲染一個react元素,通常react項目咱們都用它,渲染根部容器app

ReactDOM.render(element, container[, callback])
複製代碼

使用

ReactDOM.render(
    < App / >,
    document.getElementById('app')
)
複製代碼

ReactDOM.render會控制container容器節點裏的內容,可是不會修改容器節點自己。

hydrate

服務端渲染用hydrate。用法與 render() 相同,但它用於在 ReactDOMServer 渲染的容器中對 HTML 的內容進行 hydrate 操做。

ReactDOM.hydrate(element, container[, callback])
複製代碼

createPortal

Portal 提供了一種將子節點渲染到存在於父組件之外的 DOM 節點的優秀的方案。createPortal 能夠把當前組件或 element 元素的子節點,渲染到組件以外的其餘地方。

那麼具體應用到什麼場景呢?

好比一些全局的彈窗組件model,<Model/>組件通常都寫在咱們的組件內部,卻是真正掛載的dom,都是在外層容器,好比body上。此時就很適合createPortalAPI。

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>
    }
}
複製代碼

效果

createPortal.jpg

咱們能夠看到,咱們children實際在container 以外掛載的,可是已經被createPortal渲染到container中。

unstable_batchedUpdates

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>
    }
}
複製代碼

效果

batch1.jpg

渲染次數一次。

批量更新條件被打破

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)
    })
  }
複製代碼

效果

batch2.jpg

渲染次數三次。

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

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>
    }
}
複製代碼

先不看答案,點擊一下按鈕,打印什麼呢?

咱們來點擊一下看看

flushSync.gif

打印 0 3 4 1 ,相信不難理解爲何這麼打印了。

  • 首先 flushSync this.setState({ number: 3 })設定了一個高優先級的更新,因此3 先被打印
  • 2 4 被批量更新爲 4

相信這個demo讓咱們更深刻了解了flushSync

findDOMNode

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>
    }
}
複製代碼

效果:

findNodedom.gif

咱們徹底能夠將外層容器用ref來標記,獲取捕獲原生的dom節點。

unmountComponentAtNode

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>
    }
}
複製代碼

效果

unmounted.gif

總結

本文經過react組件層面,工具層面,hooks層面,react-dom瞭解了api的用法,但願看完的同窗,可以對着文章中的demo本身敲一遍,到頭來會發現本身成長很多。

最後, 送人玫瑰,手留餘香,以爲有收穫的朋友能夠給筆者點贊,關注一波 ,陸續更新前端超硬核文章。

提早透漏:接下來會出一部揭祕react事件系統的文章。

感興趣的同窗請關注公衆號 前端Sharing 持續推送優質好文

往期react文章

文章中,對於其餘沒有講到的react-hooks,建議你們看react-hooks三部曲。

react-hooks三部曲

react進階系列

react源碼系列

開源項目系列

參考文檔

react中文文檔

相關文章
相關標籤/搜索