React基礎(chǔ)v2

父子組件通信

  1. props -父組件向子組件傳遞數(shù)據(jù)

    • 父組件
      import Header from './Header'  // 引入子組件Header
      this.state = { msg:'123' }
      run= () => {
         // ...
      }
      render() {
          return(<div><Header msg={this.state.msg} /><div>)
      }
      
    • 子組件Header.js
      constructor(props) {  // props 是一個對象,用于接收父組件的數(shù)據(jù)
          super(props)
      }
      render() {
          return(<div>
              <span>{this.props.msg}<span>
              <button onClick={this.props.run}>Run Test</button>
          <div>)
      }
      
  2. Context API - 跨層級通信,祖代組件向任意層級的后代組件傳遞
    ps:Vue 中的 provide/inject 來源于Context
    Context模式下有兩個角色:Provider - 外層提供數(shù)據(jù)的組件,Consumer - 內(nèi)存獲取數(shù)據(jù)的組件。

    • 創(chuàng)建 Context 上下文,放在一個文件中,方便引用
      // storecontext.js
      
      import React from 'react'
      // Context 可以創(chuàng)建多個!
      const StoreContext = React.createContext(/*可以設(shè)置默認值*/)
      export default StoreContext
      
      React 渲染一個訂閱 Context 對象的組件時,這個組件會從組件樹中離自身最近的那個匹配的 Provider 中讀取到當前的 context 值;只有當組件所在的樹中沒有匹配到 Provider 時,設(shè)置的默認值才會生效。這有助于在不使用 Provider 包裝組件的情況下,對組件進行測試。
    • 在父組件中使用 Provider,它接收一個 value 屬性,當 value 值發(fā)生變化時,它內(nèi)部的所有消費組件都會重新渲染
      import StoreContext from './StoreContext'
      
      // 創(chuàng)建一個數(shù)據(jù)源
      const store = {
          name: 'Tom',
          age: 20
      }
      
      render() {
          return(
              <StoreContext.Provider value={store}>
                  <AAA></AAA>
              </StoreContext.Provider>
          )
      }
      
    • 在后代組件中使用 Consumer,它包裹一個函數(shù),回調(diào)的參數(shù)正是距離自己最近的 Provider 提供的數(shù)據(jù)源(value);如果沒有找到Provider,則回調(diào) createContext() 的默認值;
      import StoreContext from './StoreContext'
      
      render() {
          return(<StoreContext.Consumer>
              {
                  value => {
                      return(<div>
                              <p>姓名: {value.name}</p>
                              <p>年齡: {value.age}</p>
                          </div>)
                  }
              }
          </StoreContext.Consumer>)
      }
      
      # 作為 props 直接把Consumer的值傳遞給組件
      render() {
          return(<StoreContext.Consumer>
              { ctx => <TitleBar {...ctx} /> }
          </StoreContext.Consumer>)
      }
      // 函數(shù)式組件
      function TitleBar(props) {
          console.log(props)
          return <div>TitleBar</div>
      }
      
    • Class.contextType 實現(xiàn)編程式消費最近Context的值,可以在任何生命周期中訪問它
      import StoreContext from './StoreContext'
      class Titlebar extends React.Component {
          componentDidMount() {
              // StoreContext 共享的值被賦予 this.context
              let value = this.context;
          }
          render() {
              let value = this.context;
              // ...
          }
      }
      Titlebar.contextType = StoreContext;
      
  3. refs - 父組件主動獲取子組件的數(shù)據(jù)
    React提供了 refs 管理組件標簽上的自定義屬性ref;

    <Header ref="header" />
    
    this.refs.header  // 訪問子組件的數(shù)據(jù)
    
  4. 發(fā)布訂閱,第三方包如pubsub-js

  5. defaultPropspropTypes

    1. 父組件在調(diào)用子組件時,如果不給子組件傳值,則子組件可以使用 defaultProps定義默認值;
      class Header extends React.Component { ... }
      Header.defaultProps = {
          msg: '0'  //如果父組件沒有傳遞msg,則使用此默認值
      }
      export default Header;
      
    2. propTypes:用于驗證父組件傳值類型的合法性;
      import PropTypes from 'prop-types'  //react的一個內(nèi)置模塊
      Header.propTypes = {
          msg: PropTypes.number   //父組件傳遞msg的數(shù)據(jù)類型必須是number
      }
      
  6. 組件state/props的劃分原則

    1. 讓組件盡可能少狀態(tài)
      對于只有UI渲染、數(shù)據(jù)展示、沒有復(fù)雜交互的組件,應(yīng)使用props,而不是state
    2. 組件隨著時間產(chǎn)生變化的數(shù)據(jù),界面有交互,應(yīng)使用state

ref

  1. React v16.3引入了 React.createRef() 創(chuàng)建Ref,字符串類型的 Ref 將慢慢被拋棄;
    class MyComponent extends React.Component {
        constructor(props) {
            super(props);
            this.myRef = React.createRef();
        }
        render() {
            return <div ref={this.myRef} />;
        }
    }
    
    1. React 會在組件加載時設(shè)置refcurrent 屬性,在卸載時則會改回 null;
    2. ref 的更新會發(fā)生在鉤子 componentDidMount()componentDidUpdate() 之前。
  2. 訪問
    const node = this.myRef.current;
    
    current 的值取決于節(jié)點的類型
    • 普通HTML元素,值為DOM對象
    • React組件,值為實例對象
  3. 函數(shù)式組件上不能使用 ref 屬性,因為它沒有實例,但它的內(nèi)部可以使用 ref 屬性;
  4. React v16.3推薦使用 Ref 轉(zhuǎn)發(fā),從而把子組件的 Ref 暴漏給父組件;
  5. 回調(diào)式Ref
    這種設(shè)置ref的方式可以更加細致地控制 ref 的設(shè)置與解除。
    class CustomTextInput extends React.Component {
        constructor(props) {
            super(props);
            this.textInput = null;
        }
        this.setTextInputRef = element => {
            this.textInput = element;
        }
        render() {
            return <input type="text" ref={this.setTextInputRef} />
        }
    }
    
    • ref 屬性接收一個函數(shù),在組件掛載時回調(diào) ref 函數(shù),當卸載時傳入 null 并回調(diào);
    • 在組件間傳遞以回調(diào)的形式傳遞 refs
      class Parent extends React.Component {
          render() {
              return <CustomTextInput inputRef={el => this.inputElement = el} />
          }
      }
      
      function CustomTextInput(props) {
          return <input ref={props.inputRef} />
      }
      

生命周期

React V16.3之前的生命周期

V16.3之前的生命周期
生命周期
生命周期-before
  • this.forceUpdate() 手動強制觸發(fā)組件的render渲染,會導(dǎo)致組件跳過shouldComponentUpdate(),直接調(diào)用render();
    應(yīng)用場景:有些變量不在 state 上,或者 state 里的某個變量層次太深,更新時沒有自動觸發(fā)render()。
  • React V16.0 引入新的生命周期:componentDidCatch(),如果 render() 函數(shù)拋出錯誤,該函數(shù)勾子可以捕捉到錯誤信息,且可以展示相應(yīng)的錯誤提示。
    注:它只是一個增量式的修改,完全不影響原有的生命周期勾子。
  1. 組件首次加載過程中觸發(fā)的函數(shù)
    constructor --> componentWillMount --> render --> componentDidMount
    
    • componentWillMount():組件將要掛載
    • render():數(shù)據(jù)/模板渲染
    • componentDidMount():組件加載完成
    • constructor()render()在組件加載時會被觸發(fā),但并不屬于生命周期函數(shù)
  2. 組件狀態(tài)更新過程中觸發(fā)的函數(shù)
    shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate
    
    • shouldComponentUpdate():是否更新組件,常用于做組件優(yōu)化,返回 true 則更新,否則不更新。
       shouldComponentUpdate(nextProps, nextState) {
           // nextProps:表示父組件向當前組件傳遞的數(shù)據(jù)
           // nextState:當前組件更新后的數(shù)據(jù),只是還沒有重新渲染DOM
       }
      
    • componentWillUpdate():將要更新組件;
    • render():更新數(shù)據(jù)/模板;
    • componentDidUpdate():數(shù)據(jù)更新完成。
  3. 在父組件中改變 props 傳值時觸發(fā)的函數(shù):componentWillReceiveProps(),然后才會觸發(fā)組件更新的 render() 函數(shù)。
  4. 組件銷毀時觸發(fā)的函數(shù):componentWillUnmount()
    1. 父組件
      this.state = { flag:true }
      setFlag() {
          this.setState({ flag:!this.state.flag })
      }
      render() {
          return(<div>
              { this.state.flag && <Header /> }
              <button onClick={this.setFlag}>掛載與銷毀</button>
          </div>)
      }
      
    2. 子組件Header
      componentWillUnmount() {
          console.log('Header被銷毀')
      }
      
  5. 手動移除某個容器上的組件:
    ReactDOM.unmountComponentAtNode(containerDom)
    

React V16.3的新生命周期

React V16.3 的更新中,除了被熱烈討論的新 Context API 之外,新引入的兩個生命周期函數(shù) static getDerivedStateFromProps、getSnapshotBeforeUpdate 以及在未來 V17.0 版本中即將被移除的三個生命周期函數(shù) componentWillMount、componentWillReceiveProps、componentWillUpdate。這三個準備廢棄的生命周期用 getDerivedStateFromProps 替代。如果仍想使用廢棄的生命周期,可以加上前綴:UNSAFE_,如 UNSAFE_componentWillMount

React生命周期-after.png

原來的生命周期在 React v16 推出 Fiber 之后就不合適了,因為如果開啟 async rendering,在 render() 之前的所有函數(shù)都可能被執(zhí)行多次。
render() 之前執(zhí)行的生命周期:componentWillMount、componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate
除了 shouldComponentUpdate,其他三個都被靜態(tài)函數(shù)getDerivedStateFromProps取代。
這種做法強制開發(fā)者在 render() 執(zhí)行之前只做無副作用的操作,而且能做的操作局限在根據(jù) propsstate 決定新的state

  • state getDerivedStateFromProps(props, state) 會在 render() 之前被調(diào)用,并且在初始化掛載及后續(xù)更新時都會被調(diào)用,返回一個對象來更新state,如果返回null 則不執(zhí)行更新。
  • getSnapshotBeforeUpdate(prevProps, prevState)render() 之后、componentDidUpdate() 之前。使得組件能在發(fā)生更改之前從DOM中獲取一些信息(如滾動位置)。返回值將作為參數(shù)snapshot傳遞給componentDidUpdate(prevProps, prevState, snapshot)

錯誤邊界

默認清空下,若一個組件在渲染期間(render)發(fā)生錯誤,會導(dǎo)致整個組件樹全部被卸載。
錯誤邊界: 是一個組件,它能夠捕獲到渲染期間子組件發(fā)生的錯誤,并有能力阻止錯誤繼續(xù)傳播。
錯誤邊界能捕獲在渲染過程中 所有子組件的constructor和生命周期函數(shù)內(nèi)發(fā)生的錯誤。

錯誤邊界不能捕獲的錯誤類型:

  • 發(fā)生在事件處理器里面的
  • 異步代碼,如setTimeout、requestAnimationFrame
  • 服務(wù)端渲染
  • 自己本身拋出的錯誤

在代碼層面上,只要一個類組件中定義了 static getDerivedStateFromError() 或者 componentDidCatch(),它就是一個錯誤邊界(組件)。
一般來說,getDerivedStateFromError()是不允許發(fā)生副作用的,故而負責(zé)呈現(xiàn)一個備用的Fallback UI給用戶;componentDidCatch()允許發(fā)生副作用,故負責(zé)打印錯誤日志,上報錯誤到遠程服務(wù)器。

class ErrorBoundary extends React.Component {
    constructor(props) {
        super(props);
        this.state = { hasError: false };
    }
 
    static getDerivedStateFromError(error) {
        // Update state so the next render will show the fallback UI.
        return { hasError: true };
    }
 
    componentDidCatch(error, info) {
        // You can also log the error to an error reporting service
        // error 表示被拋出的錯誤
        // info 表示一個含有 ```componentStack``` 屬性的對象
        logErrorToMyService(error, info);
    }
 
    render() {
        if (this.state.hasError) {
            // You can render any custom fallback UI
            return <h1>Something went wrong.</h1>;
        }
        
        return this.props.children; 
    }
}

然后可以把它當做一個普通的組件來用:

<ErrorBoundary>
    <MyWidget />
</ErrorBoundary>

<ErrorBoundary>使用起來就像try-catch語句,只不過它是用于React組件,且保留了React原生的聲明特性。

注意: <ErrorBoundary>不能捕獲自己所產(chǎn)生的錯誤,只能捕獲在它之下的組件樹所產(chǎn)生的錯誤。
<ErrorBoundary>嵌套使用的情況下,如果某個<ErrorBoundary>不能渲染一些錯誤信息(調(diào)用static getDerivedStateFromError()失?。敲催@個錯誤就會往上冒泡到層級最近的<ErrorBoundary>。這也是try-catch語句在Javascript里的執(zhí)行機制。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容