React 0基礎學習路線(5)— 圖解詳述生命週期原理詳述(附詳細案例代碼解析過程)

1. 重點提煉

  • react 組件生命週期
  • 分類
    • 掛載階段
      • constructor(組件初始化:組件內部數據及狀態的初始化,及事件相關的初始化)
      • static getDerivedStateFromProps(對props和state進行處理,當兩者發生變化的時候,我們希望通過這個生命週期產生一個新的state,作用:對state作二次處理,要麼根據state的變化進行處理,要麼根據props的變化進行處理。大部分的情形都是通過props的變化去更新state)
      • render(無論是掛載階段還是更新階段,都會發生的生命週期,主要用來做主件渲染)
      • componentDidMount(組件解析完成後,包括把組件渲染到頁面當中,即虛擬dom解析成真實dom結構後,所執行的函數)
    • 更新階段
      • static getDerivedStateFromProps
      • shouldComponentUpdate(確定當前是否允許調用render,根據其返回的bool值來決定是否調用後面的render,主要對組件的渲染邏輯進行一定的優化)
      • render
      • getSnapshotBeforeUpdate(主件渲染完成後,準備掛載頁面的時候,發生該生命週期,主要獲取上一次的dom(結構)快照)
      • componentDidUpdate(更新完成)
    • 銷燬階段
      • componentWillUnmount(當組件被卸載,可對組件內部與組件沒有關係的一些內部數據進行清理,如定時器)
    • 錯誤
      • getDerivedStateFromError(補獲子組件發生的錯誤,一般作爲容器組件存在,即父組件捕獲,顯示一個錯誤頁面)
      • componentDidCatch(提交一個詳細的錯誤報告!發生郵件給管理員或運營人員。)

2. React.js 組件之生命週期

其實很多庫、框架,都會有很多回調函數。

如某個動畫框架:類似是以下函數,有開始、結束、移動等階段,我們可以通過回調函數傳入來擴展各個階段的功能(這裏的不同階段,就可以理解爲生命週期了)。

    animate({
        // ...設置動畫的屬性
        onstart() {
 
        },
        onend() {
 
        },
        onmove() {
 
        }
    })

2.1 生命週期

所謂的生命週期就是指某個事物從開始到結束的各個階段,當然在 React.js 中指的是組件從創建到銷燬的過程,React.js 在這個過程中的不同階段調用的函數,通過這些函數,我們可以更加精確的對組件進行控制,前面我們一直在使用的 render 函數其實就是組件生命週期渲染階段執行的函數,即生命週期中的某一個階段調用的。

2.1.1 週期分類

React.js 爲組件的生命週期劃分了四個不同的階段

  • 掛載階段(從創建到顯示在頁面的這個過程)
  • 更新階段(這個組件已經在頁面中存在,但是因爲某些東西的變化,比如狀態的變化、傳入參數發生變化都會導致這個組件重新渲染,我們稱之爲更新階段)
  • 卸載階段(我們將組件從頁面中移除了)
  • 錯誤處理

不同的階段又會對應着一些不同的函數(每一個週期,都可能調用不同的函數)

參考:http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

生成期到存在期,這一階段就是掛載階段,從它創建到出現到頁面當中,會有不同的函數參與,會有很多函數被調用,其中有些函數,我們是可以進行編程的。就相當於一個類,這個組件類中會有很多方法,有些特殊方法會主動去執行(調用),這個時候我們可以爲這個類去寫這個方法,從而好好地控制組件。

一個組件的第一個生命週期,調用的函數就是constructor(類的初始化構造函數),然後在掛載的時候,即將顯示在頁面之前,在render渲染頁面之前會調用componentWillMount方法(這個函數是在被渲染之前調用的)。

當這個函數執行完畢後緊接着調用render函數,它的作用就是將jsx編譯成虛擬dom,然後再將虛擬dom生成最終的html,即真實的dom元素,這個就是render函數執行的過程。整個渲染完成後就會調用componentDidMount方法,它稱之爲“掛載完成”,這個函數執行完代表當前我們的組件已經出現在htmldom結構當中了,即完成了完整的渲染。

這個過程完成後,我們在瀏覽器上基本就能夠看到我們的組件了,組件就會出現在頁面當中。緊接着,這個組件就在頁面上等待着用戶或者數據與它進行交互。在這個過程中,如果組件當中相關的數據發生了改變,狀態發生了改變,傳入的組件參數發生了改變等,它都會導致另外一個過程的發生。這個過程我們稱之爲更新

組件當它更新的時候,你會看到,如果是組件本身自己的state(私有數據)發生變化,它會調用shouldComponentUpdate函數,這個方法其實是早期的函數,目前它已經被修改爲另一個函數了(一會再去了解)。(注意:stateprops的更新,調用的是不同的生命週期函數。)如果是props更新,它會調用componentWillReceiveProps函數,但不管哪種方式它都會進入下一個階段,componentWillUpdate函數,緊接着又會觸發render,觸發完成之後,就會將組件重新進行渲染,整個渲染完成後就會調用componentDidUpdate方法。

並且還有一個註銷階段,即主件在頁面中被刪除了,這個時候就會觸發componentWillUnMount函數。

這整個過程只是作爲參考,因爲有些細節已經發生了變化(隨着不斷地更新)。最新大家瞭解到的叫react hooks(鉤子),它的作用與我們講的這個生命週期過程是類似的。我們需要學習這些生命週期,在什麼情況下去執行,然後我們可以在這些階段中做哪些事情,以及基本的應用場景,通過案例學習常用的生命週期。

life cycle

2.1.2 掛載階段

掛載階段是指組件創建到渲染到頁面的過程,這個過程提供了四個不同的函數

  • constructor() (對象構造函數/初始化函數,同時也是類主件)
  • render()
  • static getDerivedStateFromProps()
  • componentDidMount()

注意這裏沒有componentWillMount(),以及更新階段的componentWillReceiveProps函數和componentWillUpdate函數,因爲這是原先的,現在都被static getDerivedStateFromProps()取代了。

2.1.2.1 constructor

constructor(props)

類的構造函數,也是組件初始化函數,一般情況下,我們會在這個階段做一些初始化的工作(如:對組件內部行爲狀態進行初始化)

  • 初始化 state
  • 處理事件綁定函數的 this

2.1.2.2 render()

render 方法是 Class 組件必須實現的方法

2.1.2.2.1 example01

src/App.js

import React from 'react';
import LifeCycleDemo from "./components/LifeCycleDemo";
 
class App extends React.Component {
    
    constructor(props) {
        super(props);
    }
 
    render() {
        return (
            <div className="App">
                <LifeCycleDemo/>
            </div>
        )
    }
 
}
 
export default App;

src/components/LifeCycleDemo.js

import React from 'react';
 
export default class LifeCycleDemo extends React.Component {
    
    // 組件當中生命週期的第一個函數
    constructor(props) {
        super(props);
        console.log('組件初始化');
    }
 
    render() {
        console.log('組件開始渲染了');
        return (
            <div>
                <h2>生命週期演示</h2>
            </div>
        );
    }
}
image-20200604173818107

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.01
Branch:branch3

commit description:v2.01-example01(生命週期-constructor)

tag:v2.01

2.1.2.3 static getDerivedStateFromProps()

static getDerivedStateFromProps(props, state)

該方法會在 render 方法之前調用,無論是掛載階段還是更新階段,它的存在只有一個目的:讓組件在 props 變化時更新 state

更多細節可以上官方網站檢索:

image-20200604174752085

static getDerivedStateFromProps(props, state) 該函數也是可以接收參數的。我們打印參數看看。

2.1.2.3.1 expample02

react-Novice04\app\src\components\LifeCycleDemo.js

import React from 'react';

export default class LifeCycleDemo extends React.Component {

    // 組件當中生命週期的第一個函數
    constructor(props) {
        super(props);

        this.state = {
            a: 1
        };

        console.log('組件初始化');
    }

    static getDerivedStateFromProps(props, state) {
        // 從props中獲取派生的state
        console.log('getDerivedStateFromProps', props, state);
    }

    render() {
        console.log('組件開始渲染了');
        return (
            <div>
                <h2>生命週期演示</h2>
            </div>
        );
    }
}

react-Novice04\app\src\App.js

import React from 'react';
import LifeCycleDemo from "./components/LifeCycleDemo";

class App extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            pVal: 1
        }
    }

    render() {
        return (
            <div className="App">
                <LifeCycleDemo val={this.state.pVal}/>
            </div>
        )
    }
}

export default App;

打印了兩個對象,第一個對象其實就是props,第二個對象就是state

這個函數的作用是從props上更新state

/**
* 組件的數據(狀態)來源來自:
 *  - 傳入 props
 *  - 內部 state
*
* 當一個組件擁有一個自己的狀態(state),同時這個狀態又依賴 props 的變化的時候
*
*/

image-20200704141834021

先不用管警告。getDerivedStateFromProps發生在組件渲染之前。

image-20200704140339477

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.02
Branch:branch3

commit description:v2.02-example02(生命週期-getDerivedStateFromProps)

tag:v2.02

2.1.2.4 componentDidMount()

componentDidMount()

在組件掛載後(render 的內容插入 DOM 樹中)調用。通常在這個階段,我們可以:

  • 操作 DOM 節點(如獲取元素寬高等,建議在這個生命週期做此操作,前面的函數中做是有風險的!)
  • 發送請求(如:ajax請求、異步請求)
2.1.2.4.1 example03

組件掛載完成。

react-Novice04\app\src\components\LifeCycleDemo.js

import React from 'react';

export default class LifeCycleDemo extends React.Component {

    // 組件當中生命週期的第一個函數
    constructor(props) {
        super(props);

        this.state = {
            a: 1
        };

        console.log('組件初始化');
    }

    static getDerivedStateFromProps(props, state) {
        // 從props中獲取派生的state
        console.log('getDerivedStateFromProps', props, state);
    }

    componentDidMount() {
        console.log('componentDidMount');
    }

    render() {
        console.log('組件開始渲染了');
        return (
            <div>
                <h2>生命週期演示</h2>
            </div>
        );
    }
}

App.js

import React from 'react';
 
import LifeCycleDemo from "./components/LifeCycleDemo";
 
class App extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.state = {
            pVal: 1
        }
    }
 
    render() {
        return (
            <div className="App">
                <LifeCycleDemo val={this.state.pVal} />
            </div>
        )
    }
 
}
export default App;

image-20200604192132454

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.03
Branch:branch3

commit description:v2.03-example03(生命週期-componentDidMount)

tag:v2.03

2.1.2.4.2 example04

深入探究getDerivedStateFromProps和組件渲染。

2.1.2.4.2.1 example04-1

更新子組件的state,就會觸發getDerivedStateFromProps方法,子組件自己的狀態變化就會導致當前的子組件重新渲染。

src/components/LifeCycleDemo.js

import React from 'react';
 
export default class LifeCycleDemo extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.state = {
            a: 1
        };
 
        console.log('組件初始化');
    }
 
    static getDerivedStateFromProps(props, state) {
        // 從props中獲取派生的state
        console.log('getDerivedStateFromProps', props, state);
    }
 
    componentDidMount() {
        console.log('componentDidMount');
    }
 
    render() {
        console.log('組件開始渲染了');
        return (
            <div>
                <h2>生命週期演示</h2>
                <button onClick={() => {
                    // 更新子組件的state,就會觸發getDerivedStateFromProps方法,子組件自己的狀態變化就會導致當前的子組件重新渲染
                    this.setState({
                        a: this.state.a + 1
                    })
                }}>子組件的按鈕</button>
                <hr/>
                <p>state:{this.state.a}</p>
                <p>props: {this.props.val}</p>
            </div>
        );
    }
 
}

image-20200604193321186

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.04-1
Branch:branch3

commit description:v2.04-1-example04-1(深入探究getDerivedStateFromProps和組件渲染,子組件自己的狀態變化就會導致當前的子組件重新渲染。)

tag:v2.04-1

2.1.2.4.2.2 example04-2

同時我們父級也設置一個按鈕!

app.js

import React from 'react';
import LifeCycleDemo from "./components/LifeCycleDemo";
 
class App extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.state = {
            pVal: 1
        }
    }
 
    render() {
        return (
            <div className="App">
                <button onClick={() => {
                    this.setState({
                        pVal: this.state.pVal + 1
                    })
                }}>父組件的按鈕</button>
                <hr/>
                <LifeCycleDemo val={this.state.pVal} />
            </div>
        )
    }
 
}
export default App;

src/components/LifeCycleDemo.js

import React from 'react';
 
export default class LifeCycleDemo extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.state = {
            a: 1
        };
 
        console.log('組件初始化');
    }
 
    static getDerivedStateFromProps(props, state) {
        // 從props中獲取派生的state
        console.log('getDerivedStateFromProps', props, state);
    }
 
    componentDidMount() {
        console.log('componentDidMount');
    }
 
    render() {
        console.log('組件開始渲染了');
        return (
            <div>
                <h2>生命週期演示</h2>
                <button onClick={() => {
                    // 更新子組件的state,就會觸發getDerivedStateFromProps方法,子組件自己的狀態變化就會導致當前的子組件重新渲染. 同時我們父級也設置一個按鈕!
                    this.setState({
                        a: this.state.a + 1
                    })
                }}>子組件的按鈕</button>
                <hr/>
                <p>state:{this.state.a}</p>
                <p>props: {this.props.val}</p>
            </div>
        );
    }
}

當子組件的數據變化,它會更新與數據掛鉤的組件,即更新子組件,刷新子組件頁面。

父組件的數據變化,也會導致子組件也會更新,從這裏就能看見不一樣的東西。getDerivedStateFromProps函數在組件渲染的之前,即render方法調用之前,再調用一遍。

即渲染函數會在組件自身的 state 變化的時候,以及父組件更新的時候執行。

實際上數據變了,頁面的組件就一定會發生變化,包括其子組件,如果子組件依賴的數據也發生了變化,這個依賴的數據包括它自身的和父級傳進去的數據。

注意:一個組件中props和state的變化,都會導致組件的重新渲染。

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.04-2
Branch:branch3

commit description:v2.04-2-example04-2(深入探究getDerivedStateFromProps和組件渲染,父組件自己的狀態變化就會導致當前的子組件重新渲染。)

tag:v2.04-2

2.1.2.4.2.3 example04-3

如果子組件不更新呢?

修改:再新增一個屬性,點擊事件會改變該屬性。

app.js

import React from 'react';
 
import LifeCycleDemo from "./components/LifeCycleDemo";
 
class App extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.state = {
            pVal: 1,
            pVal2: 1
        }
    }
 
    render() {
        return (
            <div className="App">
                <button onClick={() => {
                    this.setState({
                        pVal2: this.state.pVal2 + 1
                    })
                }}>父組件的按鈕</button>
                <hr/>
 
                <LifeCycleDemo val={this.state.pVal} />
            </div>
        )
    }
 
}
 
export default App;

點擊父組件的按鈕,也會執行子組件的getDerivedStateFromProps函數,那最終會調用其render嗎?

實際上是執行了,但是我們發現調用render,但並不代表整個頁面的dom都會更新。

實際上沒有將render的返回值,拿去覆蓋原先的dom。雖然執行了render,但並沒有更新dom

一定要注意:render執行,一定不代表渲染了整個頁面(生成html,替換原先頁面上的html)。

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.04-3
Branch:branch3

commit description:v2.04-3-example04-3(深入探究getDerivedStateFromProps和組件渲染,組件實際不刷新原理。)

tag:v2.04-3

2.1.2.4.2.4 example04-4

src/components/LifeCycleDemo.js

如添加一個input

import React from 'react';
 
export default class LifeCycleDemo extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.state = {
            a: 1
        };
 
        console.log('組件初始化');
    }
 
    static getDerivedStateFromProps(props, state) {
        // 從props中獲取派生的state
        console.log('getDerivedStateFromProps', props, state);
    }
 
    componentDidMount() {
        console.log('componentDidMount');
    }
 
    render() {
        console.log('組件開始渲染了');
        return (
            <div>
                <h2>生命週期演示</h2>
                <button onClick={() => {
                    // 更新子組件的state,就會觸發getDerivedStateFromProps方法,子組件自己的狀態變化就會導致當前的子組件重新渲染. 同時我們父級也設置一個按鈕!
                    this.setState({
                        a: this.state.a + 1
                    })
                }}>子組件的按鈕</button>
                <input type="text"/>
                <hr/>
                <p>state:{this.state.a}</p>
                <p>props: {this.props.val}</p>
            </div>
        );
    }
 
}

我們在input中輸入值,點擊按鈕,發現頁面並沒有刷新(因爲input的值始終存在)。實際上React會去比較頁面,如果沒有任何變化,它是不會刷新頁面的。

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.04-4
Branch:branch3

commit description:v2.04-4-example04-4(深入探究getDerivedStateFromProps和組件渲染,組件實際不刷新原理。加入input。)

tag:v2.04-4

2.1.3 案例:郵件發送-收件人選擇

我們會發現一種狀態:

當一個組件擁有一個自己的狀態(state),同時這個狀態又依賴 props 的變化的時候。

通過以下的例子說明。

案例:[郵件發送-收件人選擇]

仿 QQ 郵箱發送郵件的添加接收人功能

2.1.3.1 example05

分析需求,可以把這頁面細分爲收件人組件,通訊錄(好友列表)組件共同組成這個頁面,緊接着我們在收件人裏可以輸入郵箱,這是一個數據,我們可以將其放入state(私有狀態)裏,即在state裏存在一個屬性可以存放收件人數據(由用戶輸入)。(關於樣式去可參考小迪的源碼,也可以自己寫!)

image-20200704143547899

2.1.3.1.1 example05-1

基本框子實現。

src/App.js

import React from 'react';
 
import LifeCycleDemo from "./components/LifeCycleDemo";
 
import Mail from "./components/Mail";
 
class App extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.state = {
            pVal: 1,
            pVal2: 1
        }
    }
 
    render() {
        return (
            <div className="App">
                
                <hr />
                <Mail />
            </div>
        )
    }
 
}
 
export default App;

src/components/mail.css

ul {
    list-style-type: none;
    margin: 0;
    padding: 0;
}
li {
    line-height: 30px;
}
.fl {
    float: left;
}
.fr {
    float: right;
}
.clear:after {
    content: '';
    display: block;
    clear: both;
}
 
.box {
    padding: 10px;
    margin: 10px;
    border: 1px solid #000;
    width: 200px;
}
 
.multi-input {
    margin: 10px;
    padding: 5px;
    border: 1px solid #666;
}
.multi-input>div {
    height: 30px;
}

src/components/Mail.js

import React from 'react';
 
import SendList from './SendList';  // 發送列表組件
import FriendList from './FriendList';  // 好友列表組件
import './mail.css';
 
export default class Mail extends React.Component{
 
    constructor(props) {
        super(props);
    }
 
    render() {
        return(
            <div>
 
                <div className="clear">
                    <h1>發送郵件</h1>
                    <hr/>
                    <div className="fl">
                        <SendList />
                    </div>
 
                    <div className="fr">
                        <FriendList />
                    </div>
                </div>
            </div>
        )
    }
}

src/components/FriendList.js

import React from 'react';
 
 
export default class FriendList extends React.Component{
 
    render() {
        return(
            <div>
                FriendList
            </div>
        )
    }
 
}

src/components/SendList.js

import React from 'react';
 
 
export default class SendList extends React.Component{
 
    constructor(props) {
        super(props);
 
        this.state = {
            users: [     // 收件人的列表 格式:{username:‘’,email:''}
                {id: 1, name: '張三', email: '[email protected]'},
                {id: 2, name: '李四', email: '[email protected]'},
                {id: 3, name: '王五', email: '[email protected]'}
            ]
        };
 
    }
 
    render() {
        return(
            <div>
 
                <div className="multi-input">
                    {
                        this.state.users.map(user => (
                            <div key={user.email}>{user.name} {user.email};</div>
                        ))
                    }
                    <div>
                        收件人:<input type="text" />
                    </div>
                </div>
 
            </div>
        )
    }
 
}

image-20200605114405342

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.05-1
Branch:branch3

commit description:v2.05-1-example05-1(案例:[郵件發送-收件人選擇]——基本框子實現)

tag:v2.05-1

2.1.3.1.2 example05-2

在當前這個組件的內部會維護一個收件人的列表,我們可以通過這個組件的input輸入框來新增收件人。

當前這個組件的users 會根據傳入的 props來新增數據,當前組件的 state 依賴了 props的數據進行更新。

如何拿到input裏的數據?

1、事件源e.target

2、通過ref

3、通過受控組件

事件源e.target

可能大家會這樣處理,但是this.state.users.push()返回的是新數組的長度,小迪也確實犯了這個錯。

react-Novice04\app\src\components\SendList.js

import React from 'react';


export default class SendList extends React.Component{

    constructor(props) {
        super(props);

        this.state = {
            users: [     // 收件人的列表 格式:{username:‘’,email:''}
                {id: 1, name: '張三', email: '[email protected]'},
                {id: 2, name: '李四', email: '[email protected]'},
                {id: 3, name: '王五', email: '[email protected]'}
            ]
        };

        this.addUser = this.addUser.bind(this);
    }

    addUser({target:{value}}) {
        this.setState({
            users: this.state.users.push()
        })
    }

    render() {
        return(
            <div>

                <div className="multi-input">
                    {
                        this.state.users.map(user => (
                            <div key={user.email}>{user.name} {user.email};</div>
                        ))
                    }
                    <div>
                        收件人:<input type="text" onKeyDown={this.addUser} />
                    </div>
                </div>

            </div>
        )
    }

}

我們可以用解構的方法去做。

import React from 'react';


export default class SendList extends React.Component{

    constructor(props) {
        super(props);

        this.state = {
            users: [     // 收件人的列表 格式:{username:‘’,email:''}
                {id: 1, name: '張三', email: '[email protected]'},
                {id: 2, name: '李四', email: '[email protected]'},
                {id: 3, name: '王五', email: '[email protected]'}
            ]
        };

        this.addUser = this.addUser.bind(this);
    }

    addUser({target:{value}}) {
        this.setState({
            // users: this.state.users.push()
            users: [...this.state.users, {email:value}]
        })
    }

    render() {
        return(
            <div>

                <div className="multi-input">
                    {
                        this.state.users.map(user => (
                            <div key={user.email}>{user.name} {user.email};</div>
                        ))
                    }
                    <div>
                        收件人:<input type="text" onKeyDown={this.addUser} />
                    </div>
                </div>

            </div>
        )
    }

}

發現還是有問題,怎麼寫一個就又來一個!!!我們應該判斷一下鍵盤的截止符(回車)。

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.05-2
Branch:branch3

commit description:v2.05-2-example05-2(案例:[郵件發送-收件人選擇]——事件源e.target 來獲取input數據,但存在問題。)

tag:v2.05-2

2.1.3.1.3 example05-3

src/components/SendList.js

import React from 'react';


export default class SendList extends React.Component{

    constructor(props) {
        super(props);

        this.state = {
            users: [     // 收件人的列表 格式:{username:‘’,email:''}
                {id: 1, name: '張三', email: '[email protected]'},
                {id: 2, name: '李四', email: '[email protected]'},
                {id: 3, name: '王五', email: '[email protected]'}
            ]
        };

        this.addUser = this.addUser.bind(this);
    }

    addUser(e) {
        // enter
        if (e.keyCode === 13) {
            this.setState({
                // users: this.state.users.push() 經典錯誤,push返回的不是數組,而是數組長度。
                // ...this.state.users解構
                users: [...this.state.users, {email: e.target.value}]
            });
        }
    }

    render() {
        return(
            <div>

                <div className="multi-input">
                    {
                        this.state.users.map(user => (
                            <div key={user.email}>{user.name} {user.email};</div>
                        ))
                    }
                    <div>
                        收件人:<input type="text" onKeyDown={this.addUser} />
                    </div>
                </div>

            </div>
        )
    }

}

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.05-3
Branch:branch3

commit description:v2.05-3-example05-3(案例:[郵件發送-收件人選擇]——事件源e.target 來獲取input數據)

tag:v2.05-3

2.1.3.1.4 example05-4

組件相互交互:通過點擊好友列表組件後,添加好友到發送列表組件中。並且收件人自己也可通過編輯添加收件人。

他倆非嵌套關係,因此不能通過props進行傳遞,可以通知父級(使用回調函數),讓父級傳遞。

react-Novice04\app\src\App.js

import React from 'react';
import LifeCycleDemo from "./components/LifeCycleDemo";
import Mail from "./components/Mail";

class App extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            pVal: 1,
            pVal2: 1
        }
    }

    render() {
        return (
            <div className="App">
                <hr />
                <Mail />
            </div>
        )
    }
}

export default App;

react-Novice04\app\src\components\Mail.js

import React from 'react';

import SendList from './SendList';  // 發送列表組件
import FriendList from './FriendList';  // 好友列表組件
import './mail.css';

export default class Mail extends React.Component{

    constructor(props) {
        super(props);

        this.state = {
            friend: null
        };

        this.clickUser = this.clickUser.bind(this);
    }

    clickUser(friend) {
        // 子級點擊選項,觸發點擊事件,緊接着執行父級回調函數clickUser
        console.log(friend);
    }

    render() {
        return(
            <div>

                <div className="clear">
                    <h1>發送郵件</h1>
                    <hr/>

                    <div className="fl">
                        {/*發送接收來的好友列表點擊的好友信息,進行轉發*/}
                        <SendList friend={this.state.friend} />
                    </div>

                    <div className="fr">
                        {/*接收好友列表點擊的好友*/}
                        {/*像事件綁定一樣,把clickUser函數傳給子級的onClickUser屬性,子組件一點擊,就觸發onClickUser的函數*/}
                        <FriendList onClickUser={this.clickUser} />
                    </div>
                </div>
            </div>
        )
    }
}

react-Novice04\app\src\components\FriendList.js

import React from 'react';


export default class FriendList extends React.Component{

    constructor(...props) {
        super(...props);

        this.state = {
            friends: [
                {id: 1, name: '張三', email: '[email protected]'},
                {id: 2, name: '李四', email: '[email protected]'},
                {id: 3, name: '王五', email: '[email protected]'}
            ]
        };

        this.clickUser = this.clickUser.bind(this);

    }

    clickUser(friend) {
        // 判斷父級有沒有傳來一個回調函數
        if ('function' === typeof this.props.onClickUser) {
            this.props.onClickUser(friend);
        }
    }

    render() {
        return(
            <div>
                <ul className="box fl">
                    {
                        // 注意不要 onClick={this.clickUser(friend)} ,這樣設置不用點擊,默認就調用了,傳參一般需要放入嵌套的函數裏
                        this.state.friends.map(friend => (
                            <li key={friend.id} onClick={() => {
                                this.clickUser(friend)
                            }}>
                                {friend.name}
                            </li>
                        ))
                    }
                </ul>

            </div>
        )
    }

}

需求:在當前這個組件的內部會維護一個收件人的列表,我們可以通過這個組件的input輸入框來新增收件人

當前這個組件的 users 會根據傳入的 props來新增數據,當前組件的 state 依賴了 props 的數據進行更新

react-Novice04\app\src\components\SendList.js

import React from 'react';


export default class SendList extends React.Component{

    constructor(props) {
        super(props);

        /**
         * 在當前這個組件的內部會維護一個收件人的列表
         * 我們可以通過這個組件的input輸入框來新增收件人
         *
         * 當前這個組件的 users 會根據傳入的 props 來新增數據
         * 當前組件的 state 依賴了 props 的數據進行更新
         */

        this.state = {
            users: [     // 收件人的列表 格式:{username:‘’,email:''}
                // {id: 1, name: '張三', email: '[email protected]'},
                // {id: 2, name: '李四', email: '[email protected]'},
                // {id: 3, name: '王五', email: '[email protected]'}
            ]
        };

        this.addUser = this.addUser.bind(this);
    }

    addUser(e) {
        // enter
        if (e.keyCode === 13) {
            this.setState({
                // users: this.state.users.push() 經典錯誤,push返回的不是數組,而是數組長度。
                // ...this.state.users解構
                users: [...this.state.users, {email: e.target.value}]
            });
        }
    }

    render() {
        return(
            <div>

                <div className="multi-input">
                    {
                        this.state.users.map(user => (
                            <div key={user.email}>{user.name} {user.email};</div>
                        ))
                    }
                    <div>
                        收件人:<input type="text" onKeyDown={this.addUser} />
                    </div>
                </div>

            </div>
        )
    }
}

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.05-4
Branch:branch3

commit description:v2.05-4-example05-4(案例:[郵件發送-收件人選擇]——父級設置回調函數,子級藉助該函數傳遞用戶點擊好友的信息)

tag:v2.05-4

2.1.3.1.5 example05-5

src/components/Mail.js

import React from 'react';
 
import SendList from './SendList';  // 發送列表組件
import FriendList from './FriendList';  // 好友列表組件
import './mail.css';
 
export default class Mail extends React.Component{
 
    constructor(props) {
        super(props);
 
        this.state = {
            friend: null
        };
 
        this.clickUser = this.clickUser.bind(this);
    }
 
    clickUser(friend) {
        // console.log(friend);
        // 更新父組件的好友列表
        this.setState({
            friend
        })
    }
 
    render() {
        return(
            <div>
 
                <div className="clear">
                    <h1>發送郵件</h1>
                    <hr/>
 
                    <div className="fl">
                        {/*發送接收來的好友列表點擊的好友*/}
                        <SendList friend={this.state.friend} />
                    </div>
 
                    <div className="fr">
                        {/*接收好友列表點擊的好友*/}
                        <FriendList onClickUser={this.clickUser} />
                    </div>
                </div>
 
            </div>
        )
    }
}

當前這個組件的 users會根據傳入的props 來新增數據,,且當前組件的 state依賴了 props 的數據進行更新

實現:通過生命週期函數

getDerivedStateFromProps會在調用 render 方法之前調用,(拿到實時的stateprops)並且在初始掛載及後續更新時都會被調用。它應返回一個對象來更新 state,如果返回 null 則不更新任何內容。

注意:state 的值在任何時候都取決於props,即state依賴於props,但不是等同關係,實際兩者一樣,就沒必要設置state了(數據還依賴於外界傳入,設置state就沒任何意義),並且官方推薦使用props,能不用state就不用。除非state依賴於props,但又受自己內在影響。

react-Novice04\app\src\components\SendList.js

import React from 'react';


export default class SendList extends React.Component{

    constructor(props) {
        super(props);

        /**
         * 在當前這個組件的內部會維護一個收件人的列表
         * 我們可以通過這個組件的input輸入框來新增收件人
         *
         * 當前這個組件的 users 會根據傳入的 props 來新增數據
         * 當前組件的 state 依賴了 props 的數據進行更新
         */

        this.state = {
            users: [     // 收件人的列表 格式:{username:‘’,email:''}
                // {id: 1, name: '張三', email: '[email protected]'},
                // {id: 2, name: '李四', email: '[email protected]'},
                // {id: 3, name: '王五', email: '[email protected]'}
            ]
        };

        this.addUser = this.addUser.bind(this);
    }

    static getDerivedStateFromProps(props, state) {
        console.log(props, state);
        if (props.friend) {
            // 如果props傳入了friend,則把傳入的friend追加到state中(state依賴於外部props)
            return {
                users: [...state.users, props.friend]
            }
        }
        return null;
    }

    addUser(e) {
        // enter
        if (e.keyCode === 13) {
            this.setState({
                // users: this.state.users.push() 經典錯誤,push返回的不是數組,而是數組長度。
                // ...this.state.users解構
                users: [...this.state.users, {email: e.target.value}]
            });
        }
    }

    render() {
        return(
            <div>

                <div className="multi-input">
                    {
                        this.state.users.map(user => (
                            <div key={user.email}>{user.name} {user.email};</div>
                        ))
                    }
                    <div>
                        收件人:<input type="text" onKeyDown={this.addUser} />
                    </div>
                </div>

            </div>
        )
    }

}

最後報錯,是因爲相同的key。我們從好友列表添加完後,再手動添加收件人,之前從好友列表添加的數據又會出現一遍!當state、props更新,getDerivedStateFromProps都會執行。我們手動添加的時候調用addUser函數,其中又會執行this.setState函數,這裏就是更新state了,導致getDerivedStateFromProps又執行一遍,導致最開始的好友列表點擊的好友重複追加了!如何解決呢?我們應該判斷當前數據是否存在,即是否允許添加!

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.05-5
Branch:branch3

commit description:v2.05-5-example05-5(案例:[郵件發送-收件人選擇]——父級設置回調函數,子級藉助該函數傳遞用戶點擊好友的信息,出現重複可以的問題)

tag:v2.05-5

2.1.3.1.6 example05-6

注意:靜態方法是屬於類(構造函數的),而不是對象,所以不能在靜態方法中使用 this 關鍵字

state、props更新,它都會執行。

getDerivedStateFromProps(props, state)作用:它的返回值將是新的state,本質上說就是通過props來更新state

src/components/SendList.js

import React from 'react';
 
 
export default class SendList extends React.Component{
 
    constructor(props) {
        super(props);
 
        this.state = {
            users: [     // 收件人的列表 格式:{username:‘’,email:''}
            ]
        };
 
        this.addUser = this.addUser.bind(this);
    }
 
    static getDerivedStateFromProps(props, state) {
        console.log(props, state);
        if (props.friend) {
 
            return {
                // getNewUsers必須設置爲靜態方法,因爲靜態方法只能調用靜態方法,不能使用this
                users: SendList.getNewUsers(props.friend, state.users)
            };
        }
        return null;
    }
 
    // 如何拿到input裏的數據?1、事件源e.target 2、通過ref 3、通過受控組件
    addUser(e) {
        // enter
        if (e.keyCode === 13) {
            this.setState({
                // users: this.state.users.push() 經典錯誤,push返回的不是數組,而是數組長度。
                // ...this.state.users解構
                users: SendList.getNewUsers({email: e.target.value}, this.state.users)
            });
            // 清空提交後的值,下次則再重新輸入E-mail
            e.target.value = '';
        }
    }
 
    // 看用戶是否存在,如果存在就不再添加了
    static getNewUsers(user, users) {
        if ( users.find(u => u.email === user.email) ) {
            return [...users];
        } else {
            return [...users, user];
        }
    }
 
    render() {
        // console.log(this.state.users)
        return(
            <div>
 
                <div className="multi-input">
                    {
                        this.state.users.map(user => (
                            <div key={user.email}>{user.name} {user.email};</div>
                        ))
                    }
                    <div>
                        收件人:<input type="text" onKeyDown={this.addUser} />
                    </div>
                </div>
 
            </div>
        )
    }
 
}

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.05-6
Branch:branch3

commit description:v2.05-6-example05-6(案例:[郵件發送-收件人選擇]——父級設置回調函數,子級藉助該函數傳遞用戶點擊好友的信息,完成)

tag:v2.05-6

2.1.4 更新階段

更新階段是指組件重新渲染的過程,組件 state 的更新(調用 setState())和父組件渲染都會觸發

  • static getDerivedStateFromProps()
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate()

2.1.4.1 static getDerivedStateFromProps()

同掛載階段,更新階段也會觸發該生命週期函數

2.1.4.2 shouldComponentUpdate()

shouldComponentUpdate(nextProps, nextState)

發生在更新階段,getDerivedStateFromProps 之後,render 之前,該函數會返回一個布爾值,決定了後續是否執行 render,首次渲染不會調用該函數

import React from 'react';
import Child from './Child';
 
export default class ShouldComponentUpdateComponent extends React.Component {
      constructor(...args) {
          super(...args);
        this.state = {
            n: 1,
        }
    }
 
      render() {
        return(
            <div>
                    <h2 onClick={e=> {
                    this.setState({n: this.state.n + 1})
                }}>n: {this.state.n}</h2>
                <Child value={this.state.n} />
              </div>
        )
    }
}
import React from 'react';
 
export default class Child extends React.Component {
 
    constructor(...props) {
        super(...props);
 
        this.state = {
            value: this.props.value
        };
    }
 
      shouldComponentUpdate(nextProps, nextState) {
        return this.state.value !== nextState.value;
    }
 
      render() {
        console.log('render');
        return(
            <div>
                value: {this.state.value}
                <button onClick={e=>{
                    this.setState({
                        value: this.state.value + 1
                    })
                }}>+</button>
            </div>
        );
    }
}

此方法僅作爲性能優化的方式而存在,不要企圖依靠此方法來“阻止”渲染,因爲可能會產生一些問題。其次,在 React.js 中本來對渲染已經做了必要的優化了,所以通過該函數本質上不能帶來特別大的明顯提升,且容易增加組件的複雜性,變得難以維護,除非確定使用它能爲當前組件帶來顯著的性能提升

官方後期也會更改該方法的特性,即使返回 false 仍可能會重新渲染組件

不推薦濫用該函數

2.1.4.2.1 example06
2.1.4.2.1.1 example06-1

app.js

import React from 'react';
 
import LifeCycleDemo from "./components/LifeCycleDemo";
 
import Mail from "./components/Mail";
 
 
class App extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.state = {
            pVal: 1,
            pVal2: 1
        }
    }
 
    render() {
        return (
            <div className="App">
                <button onClick={() => {
                    this.setState({
                        pVal2: this.state.pVal2 + 1
                    })
                }}>父組件的按鈕</button>
                <hr/>
                <LifeCycleDemo val={this.state.pVal} />
 
                <hr />
 
            </div>
        )
    }
 
}
 
export default App;

src/components/LifeCycleDemo.js

import React from 'react';
 
export default class LifeCycleDemo extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.state = {
            a: 1
        };
 
        console.log('組件初始化');
    }
 

    // static getDerivedStateFromProps(props, state) {
    //     // 從props中獲取派生的state
    //     console.log('getDerivedStateFromProps', props, state);
    // }
 
    componentDidMount() {
        console.log('componentDidMount');
    }
 
    
    render() {
        console.log('組件開始渲染了');
        return (
            <div>
                <h2>生命週期演示</h2>
                <button onClick={() => {
                    this.setState({
                        a: this.state.a + 1
                    })
                }}>子組件的按鈕</button>
                <hr/>
                <p>state:{this.state.a}</p>
                <p>props: {this.props.val}</p>
            </div>
        );
    }
 
}

無論是父組件還是子組件,都會更新,即調用render。那如何控制呢?

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.06-1
Branch:branch3

commit description:v2.06-1-example06-1(無論是父組件還是子組件,都會更新,即調用render。那如何控制呢?)

tag:v2.06-1

2.1.4.2.1.2 example06-2

可利用shouldComponentUpdate生命週期,控制子組件不刷新,即更改父組件數據子組件也不更新了。

import React from 'react';

export default class LifeCycleDemo extends React.Component {

    // 組件當中生命週期的第一個函數
    constructor(props) {
        super(props);

        this.state = {
            a: 1
        };

        console.log('組件初始化');
    }

    // static getDerivedStateFromProps(props, state) {
    //     // 從props中獲取派生的state
    //     console.log('getDerivedStateFromProps', props, state);
    // }

    componentDidMount() {
        console.log('componentDidMount');
    }

    shouldComponentUpdate(nextProps, nextState, nextContext) {
        return this.state.value !== nextState.value;
    }

    render() {
        console.log('組件開始渲染了');
        return (
            <div>
                <h2>生命週期演示</h2>
                <button onClick={() => {
                    this.setState({
                        a: this.state.a + 1
                    })
                }}>子組件的按鈕</button>
                <hr/>
                <p>state:{this.state.a}</p>
                <p>props: {this.props.val}</p>
            </div>
        );
    }
}

無論父組件更新了,還是子組件所依賴的數據發生變化,子組件也不去更新了!

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.06-2
Branch:branch3

commit description:v2.06-2-example06-2(可利用shouldComponentUpdate生命週期,控制子組件不刷新,即更改父組件數據子組件也不更新了。)

tag:v2.06-2

2.1.4.2.1.3 example06-3

我們打印參數看看:

import React from 'react';

export default class LifeCycleDemo extends React.Component {

    // 組件當中生命週期的第一個函數
    constructor(props) {
        super(props);

        this.state = {
            a: 1
        };

        console.log('組件初始化');
    }

    // static getDerivedStateFromProps(props, state) {
    //     // 從props中獲取派生的state
    //     console.log('getDerivedStateFromProps', props, state);
    // }

    componentDidMount() {
        console.log('componentDidMount');
    }

    shouldComponentUpdate(nextProps, nextState, nextContext) {
        // return this.state.value !== nextState.value;

        console.log(this.props, nextProps);
        console.log(this.state, nextState);
        return true;
    }

    render() {
        console.log('組件開始渲染了');
        return (
            <div>
                <h2>生命週期演示</h2>
                <button onClick={() => {
                    this.setState({
                        a: this.state.a + 1
                    })
                }}>子組件的按鈕</button>
                <hr/>
                <p>state:{this.state.a}</p>
                <p>props: {this.props.val}</p>
            </div>
        );
    }
}

app.js

import React from 'react';
import LifeCycleDemo from "./components/LifeCycleDemo";
import Mail from "./components/Mail";

class App extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            pVal: 1,
            pVal2: 1
        }
    }

    render() {
        return (
            <div className="App">
                <button onClick={() => {
                    this.setState({
                        pVal2: this.state.pVal2 + 1
                        // pVal: this.state.pVal + 1
                    })
                }}>父組件的按鈕</button>
                <hr/>
                <LifeCycleDemo val={this.state.pVal} />

                <hr />

            </div>
        )
    }
}

export default App;

發現打印的兩個值都是一樣的,因爲在父組件操作的是pVal2,我們改成pVal看看。然後我們就可以根據這兩個值的差異性來決定是否更新還是不更新了。

app.js

import React from 'react';
import LifeCycleDemo from "./components/LifeCycleDemo";
import Mail from "./components/Mail";

class App extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            pVal: 1,
            pVal2: 1
        }
    }

    render() {
        return (
            <div className="App">
                <button onClick={() => {
                    this.setState({
                        // pVal2: this.state.pVal2 + 1
                        pVal: this.state.pVal + 1
                    })
                }}>父組件的按鈕</button>
                <hr/>
                <LifeCycleDemo val={this.state.pVal} />

                <hr />

            </div>
        )
    }
}

export default App;

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.06-3
Branch:branch3

commit description:v2.06-3-example06-3(打印shouldComponentUpdate生命週期參數)

tag:v2.06-3

2.1.4.2.1.4 example06-4

父組件操作的是pVal2,父組件更新了pVal2,父組件實際就沒影響子組件,子組件就不去更新!除非操作的屬性與子組件相關,子組件纔會執行render方法。因此可以通過這種方式,來控制子組件到底需不需要更新。這就起到了一定的優化作用!

import React from 'react';

export default class LifeCycleDemo extends React.Component {

    // 組件當中生命週期的第一個函數
    constructor(props) {
        super(props);

        this.state = {
            a: 1
        };

        console.log('組件初始化');
    }

    // static getDerivedStateFromProps(props, state) {
    //     // 從props中獲取派生的state
    //     console.log('getDerivedStateFromProps', props, state);
    // }

    componentDidMount() {
        console.log('componentDidMount');
    }

    // nextProps nextState 均代表最新的
    shouldComponentUpdate(nextProps, nextState) {
        // return this.state.value !== nextState.value;

        console.log(this.props, nextProps);
        console.log(this.state, nextState);
        return (this.props.val !== nextProps.val || this.state.a !== nextState.a);
    }

    render() {
        console.log('組件開始渲染了');
        return (
            <div>
                <h2>生命週期演示</h2>
                <button onClick={() => {
                    this.setState({
                        a: this.state.a + 1
                    })
                }}>子組件的按鈕</button>
                <hr/>
                <p>state:{this.state.a}</p>
                <p>props: {this.props.val}</p>
            </div>
        );
    }
}

演示操作的是app的pVal2。

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.06-4
Branch:branch3

commit description:v2.06-4-example06-4(shouldComponentUpdate,通過這種方式,來控制子組件到底需不需要更新。這就起到了一定的優化作用!)

tag:v2.06-4

2.1.4.3 render()

同上

2.1.4.4 getSnapshotBeforeUpdate()

getSnapshotBeforeUpdate(prevProps, prevState)

該方法在 render() (render方法只是將jsx解析成虛擬dom,然後進行了比較)之後(在將虛擬dom解析成真實dom結構之前調用該函數,即該生命週期),但是在輸出到 DOM 之前執行,用來獲取渲染之前的快照。當我們想在當前一次更新前獲取上次的 DOM 狀態,可以在這裏進行處理,該函數的返回值將作爲參數傳遞給下個生命週期函數 componentDidUpdate。該函數的作用是,獲取真實dom更新之前上一次dom的狀態。

該函數並不常用。(官網有例子(滾動條),應用:比如有些時候要更新某個元素,需要不停添加數據,就如同聊天對話窗口一樣,這個時候滾動條可能會出現一些問題,因爲滾動條它本身不會根據內容來更新滾動條的位置,這個時候需要根據添加的內容和原始的狀態,去實時更新滾動條的位置。因爲滾動條並不是通過state來控制的(react控制的),這個時候需要根據原生dom來完成!可在這裏進行處理。)

(又如:如果有兩個元素,b元素依賴於a元素的狀態,依賴它的寬和高,即它在頁面當中上一次的狀態來進行變化,這個時候就可以在這個生命週期裏進行處理了)

2.1.4.5 componentDidUpdate()

componentDidUpdate(prevProps, prevState, snapshot)

該函數會在 DOM 更新後立即調用,首次渲染不會調用該方法。我們可以在這個函數中對渲染後的 DOM 進行操作

snapshot上一次產生的快照(getSnapshotBeforeUpdate(prevProps, prevState)的返回值),如元素渲染之後,想改變b元素的寬高,這個時候又依賴於a元素,在上一個生命週期當中返回a元素在更新之前的原始值,然後傳遞給componentDidUpdate,最終再更新b元素。

2.1.5 卸載階段

當組件從 DOM 中移除時會調用如下方法

  • componentWillUnmount()

2.1.5.1 componentWillUnmount()

componentWillUnmount()

該方法會在組件卸載及銷燬前調用,我們可以在這裏做一些清理工作,如:組件內的定時器、未完成的請求等

2.1.6 錯誤處理(比較實用)

當渲染過程,子組件的構造函數或生命週期中拋出錯誤時,會調用如下方法

  • static getDerivedStateFromError()
  • componentDidCatch()

2.1.6.1 static getDerivedStateFromError()

static getDerivedStateFromError(error)

該方法用來獲取子組件拋出的錯誤,返回值是一個對象,該對象被存儲在 state 中,在後續的 render 方法中就可以根據這個對象的值來進行處理,如:顯示不同的 UI

class ErrorBoundary extends React.Component {
      constructor(props) {
      super(props);
      this.state = { hasError: false };
  }
 
  static getDerivedStateFromError(error) {
      return { hasError: true };
  }
 
  render() {
      if (this.state.hasError) {
            return <div>出錯了</div>;
      }
      return this.props.children;
  }
}
2.1.6.1.1 example07

錯誤處理。

2.1.6.1.1.1 example07-1

react-Novice04\app\src\components\ErrorDemo.js

import React from 'react';

export default class ErrorDemo extends React.Component {

    // 組件有的時候在執行過程中,可能會不可預料地產生一些錯誤。給用戶展示不太好的體驗!
    constructor(props) {
        super(props);

        console.lo('...');
    }

    render() {
        return (
            <div>
                這是正常的頁面
            </div>
        );
    }

}

react-Novice04\app\src\App.js

import React from 'react';
import LifeCycleDemo from "./components/LifeCycleDemo";
import Mail from "./components/Mail";
import ErrorDemo from "./components/ErrorDemo";

class App extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            pVal: 1,
            pVal2: 1
        }
    }

    render() {
        return (
            <div className="App">
                {/*<button onClick={() => {*/}
                {/*    this.setState({*/}
                {/*        pVal2: this.state.pVal2 + 1*/}
                {/*        // pVal: this.state.pVal + 1*/}
                {/*    })*/}
                {/*}}>父組件的按鈕</button>*/}
                {/*<hr/>*/}
                {/*<LifeCycleDemo val={this.state.pVal} />*/}

                {/*<hr />*/}
                <ErrorDemo />
            </div>
        )
    }
}

export default App;

這個錯誤顯示出來以後一定不太好,調試階段還好說,要是上線了肯定不希望這樣顯示。上線之後就是空白一片,上線以後非開發環境,肯定不會詳細報錯了,真正錯誤就隱藏了。實際上最好的處理,有的時候出錯,可能就是一個很小的組件出錯。

image-20200605155449314

當渲染過程,子組件的構造函數或生命週期中拋出錯誤時,會調用如下方法

  • static getDerivedStateFromError() (錯誤邊界處理)
  • componentDidCatch()

邊界處理一般放在父組件進行處理,但通常情況下,會封裝一個特殊的組件,它不顯示任何東西。

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.07-1
Branch:branch3

commit description:v2.07-1-example07-1(頁面錯誤不友好提示)

tag:v2.07-1

2.1.6.1.1.2 example07-2

react-Novice04\app\src\components\ErrorBoundary.js

import React from 'react';

// ErrorBoundary 錯誤邊界
export default class ErrorBoundary extends React.Component {
    constructor(props) {
        super(props);
        this.state = { hasError: false }; // 默認沒錯
    }

    // 該方法用來獲取子組件拋出的錯誤,自動被調用
    static getDerivedStateFromError(error) {
        return { hasError: true };  // 返回的對象會更新state,類似調用this.setState({ hasError: true })
    }

    render() {
        if (this.state.hasError) {
            return <div>出錯了</div>;
        }
        return this.props.children;
    }
}

react-Novice04\app\src\App.js

import React from 'react';
import LifeCycleDemo from "./components/LifeCycleDemo";
import Mail from "./components/Mail";
import ErrorBoundary from "./components/ErrorBoundary";
import ErrorDemo from "./components/ErrorDemo";

class App extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            pVal: 1,
            pVal2: 1
        }
    }

    render() {
        return (
            <div className="App">
                {/*<button onClick={() => {*/}
                {/*    this.setState({*/}
                {/*        pVal2: this.state.pVal2 + 1*/}
                {/*        // pVal: this.state.pVal + 1*/}
                {/*    })*/}
                {/*}}>父組件的按鈕</button>*/}
                {/*<hr/>*/}
                {/*<LifeCycleDemo val={this.state.pVal} />*/}

                {/*<hr />*/}
                {/*<ErrorDemo />*/}
                <ErrorBoundary>
                    <ErrorDemo />
                </ErrorBoundary>
            </div>
        )
    }
}

export default App;

打包後的頁面,不會有問題,提示出錯了這個頁面,來進行友好提示。

注意這隻會捕獲子組件錯誤,可以把它包在根組件或者重要組件的外部!

            <ErrorBoundary>
                <重要的組件 />
            </ErrorBoundary>

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v2.07-2
Branch:branch3

commit description:v2.07-1-example07-2(頁面錯誤友好提示)

tag:v2.07-2

2.1.6.2 componentDidCatch()

componentDidCatch(error, info)

該方法與 getDerivedStateFromError() 類似,但是也有不同的地方:

  • 該方法會有一個記錄詳細錯誤堆棧信息的 info 參數
  • 該方法可以執行一些額外的操作:打印錯誤、上報錯誤信息……


(後續待補充)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章