react入門三

ReactDOM.render(<MyComponent/>, document.getElementById("root"))發(fā)生了什么

react解析MyComponent標(biāo)簽找到了MyComponent組件,發(fā)現(xiàn)組件是類定義的,隨后new出該類的實(shí)例,并通過該實(shí)例調(diào)用到原型上的render方法
將render方法返回的虛擬dom轉(zhuǎn)為真實(shí)dom,隨后呈現(xiàn)在頁面中,呈現(xiàn)后還會調(diào)用一個componentDidMount

ref

  • 字符串的形式(不推薦使用,存在性能問題)

  • 回調(diào)的方式
    <input ref={(currentNode) => {this.input = currentNode}}/>, 該回調(diào)一上來就會調(diào)用一次
    PS: 向上面這種回調(diào)的方式(內(nèi)聯(lián)的方式)在更新的時候會調(diào)用兩次,注意是更新的時候,第一次currentNode為null(因?yàn)槊看蝦ender都會創(chuàng)建一個新的內(nèi)聯(lián)函數(shù),當(dāng)更新的時候第一次調(diào)用是為了清空就得函數(shù),所以返回一個null),第二次才會把節(jié)點(diǎn)傳入. 如果非得解決這個問題,react也提供了方案,通過類綁定的方式:

    <input ref={this.saveInput}/>,

    在class里面定義saveInput

    saveInput = (c) => { // c就是傳入的node節(jié)點(diǎn)
    this.input1 = c;
    }

createRef

class Demo extends React.Compoent{
    myRef = React.createRef(); // createRef返回一個容器,該容器用于存放被ref所標(biāo)識的節(jié)點(diǎn),該容器專人專用只能綁定一個,因此后放進(jìn)去的會覆蓋之前的,如果想使用多個就需要createRef多次
    render() {
        return (
            <input ref={this.myRef}/> {/*這里相當(dāng)于將input存放到了myRef中了,input節(jié)點(diǎn)可以通過this.myRef.current訪問*/}
        ) 
    }
}

react中的事件

  • 1 通過onXXX屬性指定事件處理函數(shù)
    react中使用的是自定義事件(合成事件,而不是使用原生的dom事件)--- 是為了更好的兼容性

      react中的事件是通過事件委托的方式處理的(委托給組件最外層元素)--- 事件委托的原理是事件冒泡,使用事件委托高效
    
  • 2 通過event.target得到發(fā)生事件的dom元素對象

收集表單數(shù)據(jù)

  • 非受控組件

    現(xiàn)用現(xiàn)取,通過ref可以完成

  • 受控組件

    輸入類的元素在隨著輸入將值維護(hù)到狀態(tài)中

高階函數(shù)和柯里化

高階函數(shù):
若函數(shù)A接受的參數(shù)是一個函數(shù),那么A就稱之為高階函數(shù)
若函數(shù)A調(diào)用的返回值依然是一個函數(shù),那么A就稱之為高階函數(shù)
常見的高階函數(shù):如Promise, setTimeout, 數(shù)組的常用方式
函數(shù)柯里化
部分求值,函數(shù)的調(diào)用仍舊返回函數(shù),實(shí)現(xiàn)多次接受參數(shù)最后統(tǒng)一處理的函數(shù)編碼

生命周期

組件從創(chuàng)建到死亡會經(jīng)歷一些特定的階段

React組件中包含一系列鉤子函數(shù),會在特定的時刻調(diào)用

在定義組件時,會在特定的聲明周期回調(diào)函數(shù)中做特定的工作

  • 舊版的生命周期鉤子

組件掛載的執(zhí)行順序
constructor ---> componentWillMount ---> render ---> componentDidMount ---> componentWillUnmount

組件更新執(zhí)行順序(可以分為3條線)

父組件渲染子組件,當(dāng)傳入的數(shù)據(jù)更新時的執(zhí)行路線

componentWillReceiveProps ---> shouldComponentUpdate ---> componenWillUpdate ---> render ---> componentDidUpdate ---> componentWillUnmount

當(dāng)組件setState更新的時候

setState() ---> shouldComponentUpdate ---> componentWillUpdate ---> render ---> componentDidUpdate ---> componentWillUnmount

當(dāng)強(qiáng)制更新組件的時候(不更改狀態(tài)數(shù)據(jù)就想讓組件更新就可以走這條線)

forceUpdate() ---> componentWillUpdate ---> render ---> componentDidUpdate ---> componentWillUnmount

ps: shouldComponentUpdate是一個閥門鉤子,如果不寫這個鉤子默認(rèn)返回true,接下來的流程正常執(zhí)行,如果寫了并且返回false則接下來的流程不會執(zhí)行,只能返回true或者false,

componentWillReciveProps 這個鉤子第一次不調(diào)用,只有接受的props變化了才會調(diào)用,也就是父組件重新渲染導(dǎo)致子組件渲染才執(zhí)行該鉤子,接受新的props。

總結(jié):

初始化階段
constructor ---> componentWillMount ---> render() ---> componentDidMount
更新階段: 由組件內(nèi)部this.setState() 或者父組件更新觸發(fā)
shouldComponentUpdate ---> componentWillUpdate ---> render ---> componentDidUpdate

卸載階段:由reactDOM.unmountComponentAtNode(node)觸發(fā)
componentWillUnmount()

常用的鉤子:
componentDidMount: 一般做初始化操作例如開啟定時器,發(fā)送網(wǎng)絡(luò)請求,訂閱消息。。。

componentWillUnmount: 做一些收尾工作如取消定時器,取消訂閱等

render: 必須用,需要掉1+n次

  • 新的聲明周期 (react最新版本)

所有帶will的鉤子在新版版中都加了UNSAFE_前綴, 除了卸載的鉤子,為什么這么做,因?yàn)閞eact在設(shè)計異步渲染,這些組件可能會出現(xiàn)bug,以后可能會被廢棄

新版本廢棄了componentWillMount, componentWillUpdate, componentWillReciveProps,新增了getDerivedStateFromProps 和getSnapshotBeforeUpdate

需要注意的是 getDerivedStateFromProps不能在實(shí)例上調(diào)用,需要聲明成靜態(tài)的需要加static, 而且該方法必須要有返回值,要么你返回狀態(tài)對象,要么返回null,不能返回其他的。需要注意的是只要返回一個對象,那么狀態(tài)的更新就沒有意義了,因?yàn)橐坏┓祷貭顟B(tài),此狀態(tài)就不能被改了,該方法接收一個參數(shù)props, 他會接收props屬性并且會派生出一個取決于props的狀態(tài)??梢越邮盏诙€參數(shù)state,使用場景罕見,基本不用。

getSnapshotBeforeUpdate: 該鉤子處于render和componentDidUpdate之間,用于在更新之前獲取快照,因?yàn)楦潞笾暗臓顟B(tài)就不見了,在更新之前再看一眼之前的狀態(tài)。

componentDidUpdate() 該鉤子會接收三個參數(shù),第一個是更新前的props,第二個是更新前的state,第三個是一個快照值(即getSnapshotBeforeUpdate返回的值。)
掛載時:

constructor ---> getDerivedStateFromProps ---> render (react更新dom和refs) ---> componentDidMount

更新:

(new props | setState() | forceUpdate)時 ---> getDerivedStateFromProps(得到一個派生的狀態(tài)) ---> shouldComponentUpdate

dom的diff算法

class Person extends React.Component{
   state = {
       persons: [
           {id: 2, name: '小張', age: 23},
           {id: 3, name: '小李', age: 19}
       ]
   }

   render() {
       return (
           <ul>
               {
                   this.state.persons.map((person,index) => {
                       return <li key={index}>{person.name}</li>
                   })
               }
           </ul>
       )
   }
}


虛擬dom中的key有什么作用?

簡單說key是虛擬dom對象的標(biāo)識,在更新顯示是key起著極其重要的作用,,

詳細(xì)說,當(dāng)狀態(tài)中的數(shù)據(jù)發(fā)生變化的時候,react會根據(jù)新的數(shù)據(jù)生成新的虛擬dom,隨后react進(jìn)行新虛擬dom和就虛擬dom的diff對比,比較規(guī)則如下:
- 就虛擬dom中找到了與新虛擬dom中相同的key:
    1) 若虛擬dom中內(nèi)容沒有變化,直接使用之前的真實(shí)dom
    2) 若虛擬dom中內(nèi)容變了,則生成新的真實(shí)dom,隨后替換頁面中之前的真實(shí)dom


- 舊虛擬dom中沒有找到與新虛擬dom相同的key

    根據(jù)數(shù)據(jù)創(chuàng)建新的真實(shí)dom,隨后渲染到頁面。

用index作為key可能會引發(fā)的問題:

- 若對數(shù)據(jù)進(jìn)行逆序添加,逆序刪除等破壞順序操作,會產(chǎn)生沒有必要的真實(shí)DOM更新 ==》 界面效果沒問題,但是效率低

- 如果頁面中還包含輸入類的dom:
    會產(chǎn)生錯誤dom更新==》 界面有問題

注意,如果不存在對數(shù)據(jù)逆序添加刪除等破壞順序的操作,僅用于渲染列表,展示列表,使用index是沒有問題的

<input type="checkbox"/> 這樣的輸入內(nèi)容在react中有一個defaultChecked屬性,可以代替checked屬性,因?yàn)槭褂胏hecked,則必須使用onchange來修改其值

react組件通訊

  • 父子組件 通過props可以通信

  • 兄弟組件 可以提取共同狀態(tài)到公共父組件或者使用消息的發(fā)布訂閱,或者使用redux之類的狀態(tài)管理庫

消息的發(fā)布訂閱實(shí)現(xiàn)兄弟組件之間的通信常用的三方庫有pubsubjs

react路由

  • 前端路由的工作原理

    依托的是瀏覽器歷史記錄,可以借助history這個庫來完成操作

  • react-router-dom

    路由組件
    會自動給組件傳入路由相關(guān)的props
    NavLink可以實(shí)現(xiàn)路由的高亮,通過activeClassName指定樣式名
    標(biāo)簽體內(nèi)容是一個特殊的標(biāo)簽屬性
    通過this.props.children可以獲取標(biāo)簽體內(nèi)容
    路由組件一般放在pages目錄
    一般組件
    一般放在components目錄

    • Switch組件

      該組件會提高性能,如果不適用他,那么在匹配到目標(biāo)組件后還會繼續(xù)往下匹配,使用了他,當(dāng)匹配到目標(biāo)組件后就不會網(wǎng)下匹配其他組件了

    • 解決多級路徑頁面刷新樣式丟失的問題

      • public/index.html中引入樣式時不寫./而是寫/
      • public/index.html中引入樣式時不寫./ 而是%PUBLIC_URL%
      • 不要使用HistoryRouter而是使用HashRouter
    • 路由的嚴(yán)格模式與模糊匹配
      不是非必要情況下不要用嚴(yán)格模式,比如有二級路由的情況下使用了嚴(yán)格模式就會出問題,如/home /home/mian 當(dāng)使用了嚴(yán)格模式, 那么/home/main永遠(yuǎn)匹配不到

    • Redirect的使用

      Redirect放在注冊路由的最后,當(dāng)所有路由沒有匹配的時候提供一個默認(rèn)的路由

    • 嵌套路由

      • 注冊子路由的時候要寫上父路由的path
      • 路由的匹配是按照路由的注冊順序進(jìn)行的
    • 向路由組件傳遞params

    路由導(dǎo)航向路由組件傳遞params參數(shù)
    <Link to={`home/message/detail/${id}`}>
    // 可以傳遞多個參數(shù)
    <Link to={`home/message/detail/${id}/${title}`}> // 相當(dāng)于傳遞了一個id,一個title
    注冊的路由
    
    <Route path="/home/message/detail/:id" component={Detail}>
     // 可以接收多個參數(shù)
    <Route path="/home/message/detail/:id/:title" component={Detail}>
    Detail組件通過props參數(shù)就能接收到參數(shù),在接收到的match里面就有傳遞的參數(shù)
    
    
- 向路由組件傳遞search參數(shù)

```
<Link to={`home/message/detail/?id=${id}&title=${title}`}> // 相當(dāng)于傳遞了一個id,一個title

接受的方式(無需聲明正常注冊路由即可)
<Route path="/home/message/detail" component={Detail}>

Detail組件通過this.props.location.search獲取,獲取到的是一個字符串,我們需要的是對象的格式,因此需要特殊處理,安裝腳手架的時候其實(shí)下載了一個叫做querystring的庫,通過這個庫可以幫我們完成
import qs from 'querystring';

// 通過qs.stringify(obj) 可以將一個對象轉(zhuǎn)為key=value&key=value的格式
// 通過qs.parse(str) 可以將一個key-value格式的字符串變成對象

```
  • 向路由組件傳遞state參數(shù) (這里的state不是路由狀態(tài)的state)

    無論是params參數(shù)還是search參數(shù)都在地址欄上,還可以通過對象的方式去傳遞,此時的to屬性只能是一個對象,不再是字符串了,
    需要注意的是這種方式傳遞參數(shù)雖然地址欄上沒有參數(shù),但是刷新的時候同樣不會丟失參數(shù),因?yàn)樗谴嬖趆istory中的,如果是HashRouter刷新會丟失

```
<Link to={{pathname:`/home/message/detail`, state: {id: id, title: title}}}>

// 接受state
通過this.props.location.state可以獲取

```

- push 與 replace

歷史記錄是一個壓棧模式,默認(rèn)是push,如果要開啟replace需要這么寫, replace是替換模式
<Link replace to={{pathname:`/home/message/detail`, state: {id: id, title: title}}}>

- 編程時路由導(dǎo)航

通過腳本的方式進(jìn)行跳轉(zhuǎn)。路由組件通過this.props.history可以拿到history對象,里面包含操作歷史的方法,需要注意的是編程時路由跳轉(zhuǎn)時注冊路由需要對應(yīng),也就是說通過search跳轉(zhuǎn)的不能以params的方式注冊路由,其他同理,params和query的參數(shù)在地址欄好理解,state的不再地址欄,但是同理對應(yīng)的方式也有第二個參數(shù)可以接收state, 如this.props.history.push(path, state)

編程時路由導(dǎo)航的幾個api:
* this.props.history.push
* this.props.history.replace
* this.props.history.goBack
* this.props.history.goForward
* this.props.history.goBack
* this.props.history.go

- withRouter

路由組件里面可以獲取到操作歷史的方法,但是再非路由組件里面是沒有這些的??梢越柚鷚ithRouter使一般組件也能獲取到history
import {withRouter} from 'react-router-dom'

比如
export default withRouter(Header) ; 通過withRouter將一般組件Header加工后暴露,此時使用header組件就會有history相關(guān)的內(nèi)容


- BrowserRouter與HashRouter的區(qū)別

 * 底層原理不一樣,BrowserRouter使用的是H5的history API,不兼容Ie9及以下版本HashRouter使用的是url的hash值
 * url表現(xiàn)形式不一樣,HashRouter帶#
 * 刷新后對路由state的影響
    BrowserRouter沒有影響,因?yàn)閟tate保存在history對象中
    HashRouter會丟失state參數(shù)

* HashRouter可以用于解決一些路徑錯誤相關(guān)的問題

redux

  • redux是什么

    • 1.1 redux是專門做狀態(tài)管理的庫不是react插件
    • 1.2 可以用在react,vue,angular中,但基本與react搭配使用
    • 1.3 專門做集中式狀態(tài)管理,管理應(yīng)用中多個組件共享的狀態(tài)
  • 什么情況下用redux

    • 1.1 某個組件的狀態(tài)需要讓其他組件隨時拿到(共享)
    • 1.2 一個組件需要改變另一個組件的狀態(tài)(通信)
  • redux的工作流程

    有一個核心store,里面的值只能通過reducer更改,當(dāng)用戶在組件中派發(fā)一個action的時候。action不是特殊的東西,他是一個帶有type和數(shù)據(jù)的對象比如{type: "INCREAMENT", data: {count: 1}}就是一個action,
    通過dispatch可以將action交給store,store不干活,他會讓reducer去更改狀態(tài)。reduer將狀態(tài)加工完畢會返回一個新的狀態(tài)給store。reducer能加工狀態(tài),那個狀態(tài)哪里來?其實(shí)reducer干了兩件事,一是初始化狀態(tài),二是加工狀態(tài)。action creator是一個返回action對象的東西。

  • redux的三個核心概念

    • 1 action 是一個動作對象包含兩個屬性

      • type 標(biāo)識屬性,值為字符串,唯一,必要屬性
      • data 數(shù)據(jù)屬性,值類型任意,可選屬性

      action分同步action(一般對象) 和異步action(是一個函數(shù)),一個異步的action一定對應(yīng)一個同步的action,使用異步action需要一個中間件,因?yàn)閍ction需要的是一個對象,而不是一個函數(shù),因此需要安裝redux-thunk,引入后 需要在創(chuàng)建store的時候作為第二個參數(shù)傳遞,異步的action是通過store幫助調(diào)用的

      import {createStore, applyMiddleware} from 'redux'
      import thunk from 'redux-thunk'
      
      createStroe(reducers, applyMiddleware(thunk)) 這里的reducers是個多個reducer,以對象的方式去組織,因?yàn)閷ο蟮膋ey-value存值和取值更方便。因此reducers的結(jié)構(gòu)應(yīng)該是這樣,是多個reducer合并的結(jié)果
      
      首先引入一個函數(shù)將所有的reducer合并為一個總的reducer
      import {combineReducers} from 'redux'
      const allReducers = combineReducers({
          countRecuder,
          otherReducer,
          ...
      });
      
    • 2 reducer 用于初始化狀態(tài),加工狀態(tài),加工時根據(jù)舊的state和action,產(chǎn)生新的state的純函數(shù)

        該函數(shù)接受兩個參數(shù),第一個是之前的狀態(tài),第二個是action對象,初始化的時候第一個參數(shù)是undefined,在創(chuàng)建store的時候會傳遞reducer進(jìn)去,內(nèi)部會調(diào)用reducer進(jìn)行狀態(tài)初始化
      
        reducer必須是純函數(shù),純函數(shù),一個函數(shù)只要接受同樣的實(shí)參,那么一定得到同樣的結(jié)果,不能改寫參數(shù)的數(shù)據(jù)
      
        ```
        比如:
        let arr = [1,2,3];
        arr.push(4); 雖然arr的內(nèi)容變了,但是arr的地址引用沒有變,這樣的變化redux是不認(rèn)的。這樣寫才可以
      
        [4, ...arr];這樣得到一個新數(shù)組 
      
        像這樣的函數(shù)不是純函數(shù),同樣的輸入不能得到同樣的輸出
        function demo(a) {
            return Math.random() + a
        }
      
        function demo1(a) { // 改寫了參數(shù)的值
            a = 9
        }
      
        純函數(shù)不會產(chǎn)生任何副作用,例如網(wǎng)絡(luò)請求,輸入和輸出設(shè)備,不能調(diào)用Date.now(), Math.random()等不純的方法
        ```
      
    • 3 store 將state,action, reducer聯(lián)系到一起的對象

      通過store.getState() 可以得到狀態(tài)

      通過store.dispatch(action對象) 可以更改狀態(tài),注意不是直接更改,其實(shí)每一個action對應(yīng)一個reducer,最終是通過reducer來更改,需要注意的是redux只是在維護(hù)狀態(tài),在react中派發(fā)action導(dǎo)致轉(zhuǎn)臺改變,是不能引起頁面的渲染的。因?yàn)閞edux只承諾維護(hù)狀態(tài),沒承諾渲染頁面,如果要渲染需要我們檢測狀態(tài),手動去調(diào)用render. 注意手動調(diào)用不是this.render. 可以通過this.setState({}) 來重新渲染

      store.subscribe() 可以檢測redux的狀態(tài),只要redux中的任意狀態(tài)發(fā)生改變,指定的回調(diào)都會執(zhí)行,因此頁面一掛載就可以檢測redux的狀態(tài)了

      componentDidMount() {
      store.subscribe(() => {
      this.setState({})
      })
      }

      在組件中寫可能寫很多這樣重復(fù)的代碼,因此可以將檢測的邏輯放在入口的index.js中,這樣只要redux的狀態(tài)一發(fā)生變化,真?zhèn)€App就會重新渲染,對應(yīng)的子組件也會渲染。因?yàn)橛衐iff算法的存在,不會引起大面積的重繪重排,因此不用擔(dān)心性能問題。

react-redux

react-redux需要注意的幾個點(diǎn):

  • 所有的ui組件都應(yīng)該包裹一個容器組件,他們是父子關(guān)系
  • 容器組件是真正和redux打交道的,里面可以隨意使用redux的api
  • ui組件中不能使用任何redux的api
  • 容器組件會傳給ui組件redux中所保存的狀態(tài),用于操作狀態(tài)的方法。
  • 容器給ui傳遞狀態(tài),操作狀態(tài)的方法均通過props傳遞
  • redux的狀態(tài)變化并不會引起ui的更新,除非手動去檢測狀態(tài)變化。但是用了react-redux就不需要檢測也能更新ui, 因?yàn)槭褂胏onnect的時候他已經(jīng)幫我們檢測了。

容器組件就是一個橋梁,用于連接ui組件和redux,因此在容器組件中需要引入要包裝的ui組件,要引入store(redux的核心就是store),需要注意的是容器組件不能親自引入store,而是通過props的方式傳入store,比如有一個容器組件Count,此時可以通過props傳遞
<Count store={store}/>
需要注意的是頁面中不止這么一個容器組件,他都需要store,那么react-redux提供了一個批量提供store的方法
import {Provider} from 'react-redux';
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
)

需要引入連接的方式
import {connect} from 'react-redux'
// 得到一個容器組件
const ContainerComponent = connect()(ui組件);

容器組件和父子組件通信通過props的方式傳遞狀態(tài)和操作狀態(tài)的方法,正常的組件是通過標(biāo)簽屬性的方式給子組件傳遞參數(shù)的,但是容器組件是由connect方法形成的,并沒有和ui組件有這明確的父子關(guān)系,因此需要注意的是connect的第一個()需要兩個參數(shù)。第一個參數(shù)是一個函數(shù),用于生成傳給ui組件的狀態(tài),需要返回一個對象,既然是操作狀態(tài),那么該函數(shù)一定能接受store中的狀態(tài),該函數(shù)接受一個參數(shù)state,第二個參數(shù)同樣是一個函數(shù),(當(dāng)然也可以是一個對象,里面是一系列action,是精簡版的寫法,這這種寫法,react-redux會自動分發(fā)對應(yīng)的action),接受一個分發(fā)action的dispatch方法,返回一個對象,對象封裝的是操作狀態(tài)的方法。從語義上來講,第一個參數(shù)就是一個mapStateToProps,第二個參數(shù)就是mapDispatchToProps

示例代碼

funciton mapStateToProps(state) {
    return {
        count: state
    }
}

function mapDispatchToProps(dispatch) {
    return {
        increament: (number) =>  dispatch(createInreamentAction(number))
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(ui組件)

mapStateToProps映射狀態(tài) mapDispatchToProps映射操作狀態(tài)的對象, 這兩個函數(shù)都返回一個對象
mapDispatchToProps 也可以是一個對象,對象是一系列action,react-redux會自動分發(fā)這個action

注意事項(xiàng):當(dāng)一個ui組件對應(yīng)一個容器組件的時候,如果這樣編碼會導(dǎo)致組件的數(shù)量成倍增加,可以同過一個文件將其整合,定義ui組件,最終以容器組件暴露

redux的開發(fā)工具

一個項(xiàng)目一般都由多個人寫,可能都會用到redux存數(shù)據(jù),但是彼此之間不知道狀態(tài)是如何存的,都存了哪些狀態(tài),這時可以通過redux的開發(fā)工具來快速追蹤狀態(tài)的變化。需要引入一個庫redux-devtools-extension
在store.js中引入

import {composeWithDevTools} from redux-devtools-extension

export default createStore(allReducers,composeWithDevTools(applyMiddleware(thunk)))


關(guān)于setState

setState更新狀態(tài)的兩種寫法

  • setState(stateChange, [callbacnk]); // 對象式的寫法,更改狀態(tài)需要手動獲取原來的state
    eg: this.setState({count: this.state.count+1})
  • setState(updater, [callback]); // 函數(shù)式的寫法,不需要獲取原來的state,因?yàn)闀鳛閰?shù)傳入
    eg: this.setState((state, props) => { // 不僅能拿到state, 還可以拿到props
    return {count: state.count+1}
    })
    兩種寫法都有個可選的回調(diào),因?yàn)閞eact是異步更細(xì)的。需要注意的是setState是一個同步方法,由setState引起react更新狀態(tài)是一個異步的操作,這個回調(diào)在狀態(tài)更新完成并且在render后才調(diào)用的.

lazyLoad

路由組件的懶加載,不適用懶加載,所有的路由組件都會一次性被加載,如果有成百上千個路由組件,這是非??植赖摹B酚山M件應(yīng)該在跳那個路由就加載那個路由組件,這就是懶加載,在需要的時候才加載組件

hooks

  • React.useState(狀態(tài)初始值)

    該方法返回一個數(shù)組,數(shù)組第一位是當(dāng)前狀態(tài),第二位是更新狀態(tài)的函數(shù),因?yàn)閿?shù)組的解構(gòu)賦值是按下標(biāo)來的,所以如下:

    let [count, setCount] = React.useState(0);

    需要注意的是每次調(diào)用setCount更新,函數(shù)組件都會執(zhí)行,setCount的參數(shù)可以是一個函數(shù),

  • React.useEffect(fn,[arr]) 可以在函數(shù)式組件里模擬生命周期鉤子,fn就相當(dāng)于一個聲明周期鉤子至于是哪一個需要看情況

    如果不指定第二個參數(shù)arr,那么所有的狀態(tài)的改變都會執(zhí)行回調(diào)fn。如果arr是空,表示全都不監(jiān)測,如果不寫第二個參數(shù)那么表示全部進(jìn)行監(jiān)測,如果要監(jiān)聽指定的狀態(tài),就需要在數(shù)組中指明。fn可以返回一個函數(shù)。

  • React.useRef()
    和createRef一樣的作用

  • React.createContext

    1. 創(chuàng)建context容器對象
      const xxxContext = React.createContext();

    2. 渲染子組件時,外面包裹xxxContext.Provider,通過value屬性給后代組件傳遞數(shù)據(jù)
      <xxxContext.Provider value={數(shù)據(jù)}>
      子組件
      </xxxContext.Provider>

    3)后代組件讀取數(shù)據(jù)
    方式一,僅適用于類組件
    首先聲明接收 static contextType = xxxContext 然后才能通過this.context.value拿到

      方式二,函數(shù)式組件和類組件都能接收到
      <xxxContext.Consumer>
          {
    
              value => ( // value就是context中的value數(shù)據(jù)
                  要顯示的內(nèi)容
              )
          }
    
      </xxxContext.Consumer>
    

    注意,在應(yīng)用開發(fā)中一般不用context,一般都用它來封裝react插件。

組件優(yōu)化

- Component的兩個問題
    1) 只要執(zhí)行setState(), 即使不改變狀態(tài)數(shù)據(jù),組件也會重新render() ,也就是setState({}) 也會render.
    2) 只要當(dāng)前組件重新render,就會自動重新render子組件 ,效率低。

  

    高效的做法應(yīng)該是
        只有當(dāng)前組件的state或者props數(shù)據(jù)發(fā)生改變時才重新render

    之所以有這樣的問題是因?yàn)镃omponent中的shouldComponentUpdate()默認(rèn)總是返回true. 通過重寫這個鉤子可以解決這個問題

    shouldComponentUpdate(nextProps, nextState){
        
        console.log(this.props, this.state) // 當(dāng)前的props和state
        console.log(nextProps, nextState); // 目標(biāo)props和state
        // 根據(jù)當(dāng)前數(shù)據(jù)和目標(biāo)數(shù)據(jù)對比來決定返回ture|false ,至于怎么比有人做了,react中提供了PureComponet這個組件,他會重寫 shouldComponentUpdate, 里面的對比邏輯是寫好了的
        reuturn true;
    }

- PureComponent 

    該組件其實(shí)也沒干啥,就是重寫了shouldComponentUpdate, PureComponent也會有一點(diǎn)小瑕疵。因?yàn)樗牡讓舆M(jìn)行了淺對比,因此如果是下面的這種寫法同樣會有問題

    const obj = this.state;
    obj.xxx = 'XXX'; // 改了某一個屬性
    this.setState(obj); // 因?yàn)閛bj和state指向了同一個引用,因此在使用PureComponent比較時認(rèn)為state是沒有變化的,所以不會render.因此一定注意使用PureComponent,在更新狀態(tài)時不要和原來的狀態(tài)發(fā)生任何關(guān)系。使用時額外注意數(shù)組的那些方法。

renderProps

場景,比如有兩個組件A,B,不應(yīng)該關(guān)系不大,但是因?yàn)槟承┻壿嫷脑蚩赡苄枰@么寫,

<A>
    <B/>
</A>
對于這種格式要個可以通過this.props.children來獲取,但是有個問題如果B組件需要A組件內(nèi)的數(shù)據(jù)是做不到的,因此就有了下面的這種格式

<A render={(parmas) => <B {...params}/>}/> // 就相當(dāng)于傳遞了一個函數(shù),該函數(shù)返回一個組件,函數(shù)名不一定叫render.

在A組件中預(yù)留一個位置this.props.render(可以傳遞A中的狀態(tài)到B),類似于vue中插槽,

組件通信的總結(jié)

* 組件間的關(guān)系

    1) 父子組件
    2) 兄弟組件 (非嵌套組件)
    3) 祖孫組件(跨級組件)

* 幾種通信方式

    1) props
        1.1) children props
        1.2) render props

    2) 消息的發(fā)布訂閱
            pubsub ,event等
    3) 集中式管理如redux, dva等

    4) context:
            生產(chǎn)者和消費(fèi)者模式

快速預(yù)覽打包后的程序

全局安裝serve,會快速開啟一個服務(wù)。

錯誤邊界

一個頁面有多個組件構(gòu)成,但是其中的某個組件因?yàn)槟承┎豢煽匾蛩貙?dǎo)致出錯,此時整個頁面都會出問題,邊界錯誤就是處理這類問題的,就是當(dāng)某個組件發(fā)生了錯誤,不要導(dǎo)致整個頁面崩潰,而是將錯誤控制在最小的范圍了,出錯了給一個友好的提示。需要注意的是錯誤邊界只有在生產(chǎn)環(huán)境有效,就是打包后才生效,開發(fā)環(huán)境是不生效的??偟膩碚f錯誤邊界就是用來捕獲后代組件錯誤,渲染出備用頁面,只能捕獲后代組件生命周期產(chǎn)生的錯誤,不能捕獲自己組件產(chǎn)生的錯誤和其他組件在合成事件,定時器中產(chǎn)生的錯誤。

當(dāng)Parent的子組件出錯時會走這個鉤子,定義在父組件,并且攜帶錯誤信息err
state = {
    error: '' // 默認(rèn)空,當(dāng)發(fā)生了錯誤就會有值,可以根據(jù)這個值給一個友好的提示
}
static getDerivedFromError(err) {
    return {error: err}
}

當(dāng)然還有一個鉤子componentDidCatch() {
    console.log('渲染組件出錯');
} 當(dāng)組件發(fā)生錯誤的時候就會觸發(fā)該鉤子,這里可以統(tǒng)計發(fā)生錯誤的次數(shù)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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