react-03-生命周期和組件通信

生命周期與鉤子函數(shù)(重點(diǎn))

生命周期指的react實(shí)例及組件從創(chuàng)建到運(yùn)行到銷毀的完整的過(guò)程。

組件的生命周期可分成三個(gè)階段(狀態(tài)):

  • Mounting:創(chuàng)建階段:已插入真實(shí) DOM
  • Updating:運(yùn)行階段:正在被重新渲染
  • Unmounting:銷毀階段:已移出真實(shí) DOM

鉤子函數(shù)指提前埋在生命周期中的函數(shù),等到程序運(yùn)行到這一刻時(shí),它會(huì)自動(dòng)執(zhí)行。

常用的鉤子函數(shù)

componentWillMount、componentWillReceiveProps、shouldComponentUpdate、ComponentDidMount

代碼示例:

父組件代碼

class Comp1 extends React.Component{
    constructor(){
        super();
        this.state = {
            a:1,
            isShow:true
        }
    }
    up_click(){
        this.setState(state=>({
            a:state.a+1
        }));
    }
    un_click(){
        this.setState(state=>({
            isShow:!state.isShow
        }));
    }   
    render(){
        return (<div>
            <input type="button" onClick={()=>{this.up_click()}} value="更改組件2的屬性" />
            <input type="button" onClick={()=>{this.un_click()}} value="卸載組件2" />
            {this.state.isShow ? <Comp2 b={this.state.a} /> : null}
        </div>);
    }
}
ReactDOM.render( <Comp1 />, document.getElementById('root'));

子組件代碼

class Comp2 extends React.Component{
    // 創(chuàng)建階段
    constructor(){
        super();
        console.log("constructor");
    }
    componentWillMount(){
        console.log("componentWillMount在渲染前調(diào)用");
    }
    render(){
        console.log("render");
        return <div id='div2'>comp2-{this.props.b}</div>
    }
    componentDidMount(){
        console.log("componentDidMount在第一次渲染后調(diào)用");
    }

    // 運(yùn)行中階段
    componentWillReceiveProps(newProps) {
        console.log(`newProps: ${newProps}
        在組件接收到一個(gè)新的 prop (更新后)時(shí)被調(diào)用。這個(gè)方法在初始化render時(shí)不會(huì)被調(diào)用。`)
    }
    shouldComponentUpdate(newProps, newState) {
        console.log(`newProps: ${newProps} newState: ${newState} 
        返回一個(gè)布爾值。在組件接收到新的props或者state時(shí)被調(diào)用。
        在初始化時(shí)或者使用forceUpdate時(shí)不被調(diào)用??梢栽谀愦_認(rèn)不需要更新組件時(shí)使用。`)
        return true;    // true表示更新組件;false表示不更新組件
    }
    componentWillUpdate(nextProps, nextState) {
        console.log(`nextProps: ${nextProps} nextState:${nextState} 
        在組件接收到新的props或者state但還沒(méi)有render時(shí)被調(diào)用。在初始化時(shí)不會(huì)被調(diào)用。`);
    }
    componentDidUpdate(prevProps, prevState) {
        console.log(`prevProps:${prevProps} prevState:${prevState} 
        在組件完成更新后立即調(diào)用。在初始化時(shí)不會(huì)被調(diào)用。`)
    }

    // 銷毀階段
    componentWillUnmount() {
        console.log('在組件從 DOM 中移除的時(shí)候立刻被調(diào)用')
    }
}

新增的生命周期鉤子函數(shù)

在 react v16.3 時(shí),新引入了新的生命周期函數(shù):getDerivedStateFromProps,getSnapshotBeforeUpdate。

在未來(lái)的 react v17 時(shí),componentWillMount、componentWillReceiveProps、componentWillUpdate 要被廢棄。

getDerivedStateFromProps

// 父組件的state發(fā)生變化,導(dǎo)致父組件中的當(dāng)前組件被重新渲染,當(dāng)前組件的props被修改時(shí),該鉤子函數(shù)會(huì)被觸發(fā)。
/*componentWillReceiveProps(nextProps){
    console.log('nextProps: ', nextProps);
}*/


// 不僅僅具有componentWillReceiveProps的能力,自身組件state變化時(shí),該鉤子函數(shù)也會(huì)被觸發(fā)。
// 該函數(shù)在shouldComponentUpdate之前執(zhí)行。
// static描述是靜態(tài)函數(shù),其沒(méi)有this指向,所以無(wú)權(quán)操作實(shí)例,所以更安全,而且消耗性能低。
// nextProps 傳入后的prop數(shù)據(jù),即最新的props
// prevState 相對(duì)于合并的state來(lái)說(shuō)的前一個(gè)狀態(tài)
static getDerivedStateFromProps(nextProps, prevState) {
    console.log('nextProps: ', nextProps);
    console.log('prevState: ', prevState);
    return {x:nextProps.a} // 合并到當(dāng)前組件的state
    //return null
}

配合 componentDidUpdate 周期函數(shù),getDerivedStateFromProps 是為了替代 componentWillReceiveProps 而出現(xiàn)的。它將原本 componentWillReceiveProps 功能進(jìn)行劃分 —— 更新 state 和 操作/調(diào)用 props,很大程度避免了職責(zé)不清而導(dǎo)致過(guò)多的渲染, 從而影響應(yīng)該性能。

getSnapshotBeforeUpdate

在 render 之后執(zhí)行的鉤子

// 更新之前
getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log('getSnapshotBeforeUpdate')
    console.log('prevProps:', prevProps)
    console.log('prevState:', prevState)
    return {x:1}
}

// 更新之后 snapshot能夠得到 getSnapshotBeforeUpdate 的返回值
componentDidUpdate(prevProps, prevState, snapshot) {
    console.log('componentDidUpdate')
    console.log('prevProps:', prevProps)
    console.log('prevState:', prevState)
    console.log('snapshot:', snapshot)
}

性能優(yōu)化 shouldComponentUpdate()

決定視圖是否需要重新渲染

改變a時(shí),render重新執(zhí)行;改變b時(shí),render不會(huì)重新執(zhí)行

class App extends Component {  
    constructor(){
        super();
        this.state = {
            a : 1,
            b : 1
        }
        this.fna = ()=>{
            this.setState({ a: new Date().getTime() })
        }
        this.fnb = function(){
            this.setState({ b: new Date().getTime() })
        }
    }
    shouldComponentUpdate(nextProps, nextState){
        if( this.state.a !== nextState.a ){
            return true;
        }else{
            return false;
        }
    }
    render(){
        return <div>
            a: {this.state.a}<br />
            b: {this.state.b}<br />
            <input type="button" value="改變a" onClick={this.fna} />
            <input type="button" value="改變b" onClick={()=>this.fnb()} />
        </div>;
    }
}

純組件 PureComponent(淺比較)

pure 是純的意思,PureComponent 也就是純組件

淺比較,如果是PureComponent,那么執(zhí)行add時(shí),視圖不會(huì)更新;

在修改純組件中的狀態(tài)時(shí),檢查更新前后的狀態(tài)是否一致(棧中比較),如果一致,則不更新視圖,如果不一致,才更新視圖。

而如果是React.Component,那么當(dāng)執(zhí)行add時(shí),視圖會(huì)自動(dòng)更新。

比較修改狀態(tài)前后是否一致時(shí),在堆中比較。

class App extends React.PureComponent {
    constructor(props) {
        super(props);
        this.state = { 
            a : 1,
            arr : ['a','b','c']    
        }
    }
    updata(){
        this.setState({
            a: this.state.a+1
        })
    }
    add(){
        this.setState(state=>{
            state.arr.push( new Date().toLocaleString() );
            return state;
        })
    }
    render() {
        return (
            <ul>
                <li>
                    <input
                        type="button" 
                        value={'修改:'+this.state.a}
                        onClick={this.updata.bind(this)} />
                    <input
                        type="button"
                        value="添加"
                        onClick={this.add.bind(this)} />
                </li>
                { this.state.arr.map((item, ind)=><li key={ind}>{item}</li>)  }
            </ul>
        );
    }
}

使用 PureComponent 可能導(dǎo)致不自動(dòng)更新頁(yè)面

因?yàn)镻ureComponent是淺比較,所以對(duì)數(shù)組和對(duì)象的更新,如果只是改變了堆中數(shù)據(jù),那么系統(tǒng)是不會(huì)自動(dòng)觸發(fā)render函數(shù)的,就不會(huì)自動(dòng)更新了,這個(gè)過(guò)程在react中被稱為數(shù)據(jù)的突變。

把PureComponent換成Component就不會(huì)出現(xiàn)這個(gè)問(wèn)題了,但Component屬于深比較,性能消耗的多一些。

不會(huì)突變的數(shù)據(jù)力量

PureComponent淺比較中如何觸發(fā)render?

只要改變了棧中的數(shù)據(jù),視圖層就會(huì)自動(dòng)更新。

this.setState(prevState => ({
    words: prevState.words.concat(['marklar'])
}));
this.setState(prevState => ({
    words: [...prevState.words, 'marklar'],
}));
Object.assign({}, colormap, {right: 'blue'});

以上代碼都可以在PureComponent組件中,觸發(fā)render。

父組件向子組件傳遞數(shù)據(jù)(重點(diǎn))

react 和 vue 和 angular 一樣,都是單項(xiàng)數(shù)據(jù)流,所以項(xiàng)目中推薦的是父向子傳遞數(shù)據(jù)。

狀態(tài)提升

非父子組件數(shù)據(jù)通信時(shí),把一些共有的數(shù)據(jù)放到共有的節(jié)點(diǎn)中。

爺爺、大伯、父親、孩子

孩子:修改大伯的狀態(tài)時(shí),應(yīng)該把大伯的狀態(tài)提升到爺爺上,然后由爺爺以屬性的形式,把方法先傳給父親,然后父親以屬性的形式把方法傳給孩子,孩子觸發(fā)該方法,就能觸發(fā)爺爺上的方法,爺爺修改了狀態(tài),重新傳給大伯,大伯重新渲染頁(yè)面。

父組件在屬性上描述想要傳入的數(shù)據(jù)即可

<abc xyz="123"></abc>

子組件使用 props 接收傳入的數(shù)據(jù)即可

this.props.xyz

子組件向父組件傳遞數(shù)據(jù)

父組件:

fn(a, b){
    alert(a+b)
}
render(){
    return ( <div>
        父組件 <br/>
        <abc fn={this.fn.bind(this)}></abc>
    </div> )
}

子組件:

<button onClick={()=>{ this.props.fn(1,2) }} >按鈕</button>

EventBus 中央事件總線

非父子組件,數(shù)據(jù)通信,eventbus。

bus.js

import { Component } from 'react'
import { EventEmitter } from 'events'
const bus = new EventEmitter();
Component.prototype.$bus = bus;

index.js

import './modules/bus.js'

創(chuàng)建自定義事件

this.$bus.on('abc', function(){})

觸發(fā)自定義事件

this.$bus.emit('abc')

emitter對(duì)象下還有once、off等方法

Context 狀態(tài)樹

context 狀態(tài)樹雖然沒(méi)有被廢除,但官方是不建議使用的。

解決的是復(fù)雜組件關(guān)系時(shí),數(shù)據(jù)共享的問(wèn)題,官方建議用eventbus或redux來(lái)解決。

Provider 提供者;Consumer 消費(fèi)者

// 創(chuàng)建名字叫做colorContext的狀態(tài)樹上下文對(duì)象,默認(rèn)值為red。
const colorContext = React.createContext('red');

// 創(chuàng)建外層組件(Provider提供了一些數(shù)據(jù)共享的能力,表示colorContext這顆狀態(tài)樹對(duì)象的值設(shè)置為yellow)
// 即,當(dāng)前組件的后代組件,都可以通過(guò)Consumer來(lái)使用共享中的數(shù)據(jù),即yellow這個(gè)數(shù)據(jù)。
class Container extends Component {
    render(){
        return <colorContext.Provider value='yellow'>
            <div> Container
                <Temp2></Temp2>
            </div>
        </colorContext.Provider>
    }
}

// 創(chuàng)建中間層組件(復(fù)雜的組件關(guān)系時(shí),這可能是很多層,如果使用context,就不需要一層一層的傳遞props了)
class Temp2 extends Component {
    render(){
        return <div> temp
            <Box></Box>
        </div>
    }
}

// 創(chuàng)建內(nèi)層組件(在這層組件中,使用前面提供的數(shù)據(jù))
class Box extends Component {
    render(){
        return <colorContext.Consumer>
            {c=><div> box
                <div style={{background:c}}>{c}</div>
            </div>}
        </colorContext.Consumer>
    }
}

把 Container 類中的 colorContext 這個(gè)標(biāo)簽去掉,直接在 Box 類中用 colorContext 就能夠看到默認(rèn)值了,注意return 后面不能有換行。

HOC 高階組件

高階組件(HOC)是react中的高級(jí)技術(shù),用來(lái)重用組件邏輯。

高階組件就是一個(gè)函數(shù),且該函數(shù)接受一個(gè)組件作為參數(shù),并返回一個(gè)新的組件。

// 原始組件
class OldComp extends Component {   
    render(){
        return <div>old</div>
    }
}

// 高階組件
function higherOrderComponent(Comp){
    return class extends React.Component {
        render(){
            return <Comp />;
        }
    }
}

// 新組件
const NewComp = higherOrderComponent(OldComp);

// App組件的渲染
class App extends Component {  
    render(){
        return <NewComp />;
    }
}

Slot 插槽

基本

組合---包含關(guān)系---slot

class Component1 extends Component {   
    render(){
        return <div>
            { this.props.children }
        </div>;
    }
}

class App extends Component {
    constructor(){
        super();
        this.state = {}
    }
    render(){
        return (            
            <Component1>
                <h1>標(biāo)題</h1>
                <p>內(nèi)容</p>
            </Component1>
        );
    }
}

多個(gè)

class Component1 extends Component {   
    render(){
        return <div>
            { this.props.left }
            { this.props.right }
            { this.props.children }
        </div>;
    }
}

class App extends Component {
    constructor(){
        super();
        this.state = {}
    }   
    render(){
        return (            
            <Component1
                left={<div>你好</div>}
                right={<div>hello</div>}
            >
                children僅解析這內(nèi)容,不會(huì)取left和right
            </Component1>
        );
    }
}

Flux 狀態(tài)管理

Flux 是比較舊的一種狀態(tài)管理技術(shù),現(xiàn)在 react 官方已經(jīng)不推薦使用了。

網(wǎng)上看到的 flux 教程幾乎都是2015-2017年的,而當(dāng)時(shí)的 react 和現(xiàn)在的 react 的代碼寫法上有很大區(qū)別。

下面是新代碼的寫法。

App.js 組件視圖層入口

用戶打開瀏覽器后看到的頁(yè)面,這個(gè)頁(yè)面有2部分功能。

  • 從 store 中獲取數(shù)據(jù),渲染到當(dāng)前組件的視圖層上。
  • 點(diǎn)擊按鈕,觸發(fā) action 中的方法,這個(gè)方法改變 store 中的數(shù)據(jù)。(store數(shù)據(jù)改變后,當(dāng)前 App 組件重新渲染)
import React, { Component } from 'react';

// 所有的動(dòng)作
import Actions from './store/actions';

// 倉(cāng)庫(kù),這里保存的是數(shù)據(jù)和操作數(shù)據(jù)的方法
import store from './store/store';

// 組件
class App extends Component {
    constructor(){
        super();
        this.state = {
            todos : store.todos
        }
    }
    
    // 執(zhí)行 action 中的方法
    add(){
        Actions.add('你好');
    }
    
    // 注冊(cè)一個(gè)回調(diào)函數(shù),因?yàn)?flux 中的數(shù)據(jù)修改后,不會(huì)自動(dòng)更新視圖,
    // 所以向 store 中注冊(cè)一個(gè)函數(shù),
    // 等 store 中數(shù)據(jù)發(fā)生變化后,要調(diào)用這個(gè)回調(diào)函數(shù),進(jìn)而修改當(dāng)前視圖。
    componentDidMount(){
        store.change(()=>{
            this.setState({
                todos : store.todos    
            })
        })
    }
    render() {
        return (
            <div>
                <button onClick={()=>{this.add()}}>添加</button>
                { this.state.todos.map(item=><li key={item.id}>
                    {item.text}
                </li>) }
            </div>
        );
    }
}

export default App;

action.js 動(dòng)作

動(dòng)作頁(yè)面,所有操作 flux 的動(dòng)作都寫在此處,比如對(duì) store 中某數(shù)據(jù)的增刪改查操作的動(dòng)作。

實(shí)際上是調(diào)用 dispatcher 的 dispatch 方法。

import appDispatcher from './dispatcher';

export default {
    add( val ){
        appDispatcher.dispatch({type:'ADD', val});
    }
}

dispatcher.js 派發(fā)

只做一些派發(fā),業(yè)務(wù)邏輯都寫在 store 中

// npm i flux  or  yarn add flux
import { Dispatcher } from 'flux';
import store from './store';

// 創(chuàng)建 dispatcher
const appDispatcher = new Dispatcher();

// 當(dāng)用戶執(zhí)行appDispatcher.dispatch時(shí),實(shí)際上執(zhí)行的就是下面注冊(cè)進(jìn)來(lái)的函數(shù)
appDispatcher.register(action => {
    // console.log('dispatcher -> action:', action);
    switch( action.type ){
        case 'ADD':
            store.addTodo( action.val );
            store.emit('change'); // 觸發(fā)用戶提交過(guò)來(lái)的回調(diào)函數(shù)
            break;
    }
})

export default appDispatcher;

store.js 倉(cāng)庫(kù)

倉(cāng)庫(kù),存儲(chǔ)數(shù)據(jù)的容器。

import { EventEmitter } from 'events';

// 創(chuàng)建store對(duì)象,讓store對(duì)象具有on和emit方法(Object.assign是將對(duì)象進(jìn)行合并)
const store = Object.assign({}, EventEmitter.prototype, {
    todos : [],
    addTodo( val ){
        this.todos.push({text:val, id:new Date().getTime()})
    },
    change( callback ){
        this.on('change', callback);
    }
});

export default store;

Yarn

yarn 和 npm 一樣,都是包管理工具,解決的都是在項(xiàng)目中,對(duì)文件的上傳、下載、依賴描述等等相關(guān)問(wèn)題。

作用 npm Yarn
安裝 npm install(i) yarn
卸載 npm uninstall(un) yarn remove
全局安裝 npm install xxx –-global(-g) yarn global add xxx
安裝包 npm install xxx –save(-S) yarn add xxx
開發(fā)模式安裝包 npm install xxx –save-dev(-D) yarn add xxx –dev(-D)
更新 npm update –save yarn upgrade
全局更新 npm update –global yarn global upgrade
卸載 npm uninstall [–save/–save-dev] yarn remove xx
清除緩存 npm cache clean yarn cache clean
重裝 rm -rf node_modules && npm install yarn upgrade
react生命周期.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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