02基礎(chǔ)語(yǔ)法--005--React 技術(shù)詳解

目錄

(一)React 簡(jiǎn)介

  • 主要作用是為 MVC 模式中的視圖(view)層構(gòu)建界面視圖
  • 還可以以插件的形式作用于 Web 應(yīng)用程序的非視圖部分邀泉,實(shí)現(xiàn)與其他 JavaScript 框架的整合

DOM发皿,文檔對(duì)象模式

W3C組織推薦的處理可擴(kuò)展標(biāo)志語(yǔ)言的標(biāo)準(zhǔn)編程接口

DOM

1. 傳統(tǒng)的HTML 網(wǎng)頁(yè)開(kāi)發(fā)

  1. 直接操作 DOM,需要非常大的開(kāi)銷郁妈;
  2. 更新頁(yè)面內(nèi)容或元素,需要將整個(gè)頁(yè)面重新繪制;

2. React 性能優(yōu)化方案(刷新邏輯)

  1. React 底層設(shè)計(jì)了一個(gè)虛擬的 DOM;
  2. 虛擬的 DOM 與頁(yè)面真實(shí)的 DOM 進(jìn)行映射躁愿;
  3. 當(dāng)數(shù)據(jù)變化時(shí)
    1. React 會(huì)重新構(gòu)建 DOM 樹(shù);
    2. 通過(guò)底層的 diff 算法找到 DOM 的差異部分沪蓬;
    3. 瀏覽器只需要更新變化的部分彤钟;

3. React 的跨平臺(tái)方案

借助虛擬的 DOM 技術(shù)來(lái)實(shí)現(xiàn)服務(wù)端應(yīng)用、Web 應(yīng)用和移動(dòng)手機(jī)應(yīng)用的跨平臺(tái)開(kāi)發(fā)

4. React 數(shù)據(jù)的單向流向

  1. 數(shù)據(jù)默認(rèn)從父節(jié)點(diǎn)傳到子節(jié)點(diǎn)怜跑;
  2. 父節(jié)點(diǎn)數(shù)據(jù)通過(guò) props 傳遞到子節(jié)點(diǎn)样勃,如果父節(jié)點(diǎn)的 props 值發(fā)生改變吠勘,那么其所有子節(jié)點(diǎn)也會(huì)執(zhí)行重新渲染操作;
  3. 好處:使得組件足夠扁平峡眶,更加便于維護(hù)剧防。

(二)React 組件詳解

2.1 React 組件基礎(chǔ)知識(shí)

組件定義

  • 組件是 React 的核心內(nèi)容
  • 組件是視圖頁(yè)面的重要組成部分
  • 每一個(gè)視圖頁(yè)面都由一個(gè)或多個(gè)組件構(gòu)成
  • 組件是 React 應(yīng)用程序的基石

組件分類:無(wú)狀態(tài)組件

沒(méi)有狀態(tài)的組件,只做純靜態(tài)展示

  • 無(wú)狀態(tài)組件是最基本的組件存在形式
  • 構(gòu)成:由 props屬性 和 render渲染函數(shù) 構(gòu)成
  • 好處:由于不涉及狀態(tài)的更新辫樱,復(fù)用性最強(qiáng)

組件分類:有狀態(tài)組件

在無(wú)狀態(tài)的組件的基礎(chǔ)上增加了組件內(nèi)部狀態(tài)管理

  • 生命周期:有狀態(tài)組件有生命周期峭拘,在不同的時(shí)刻觸發(fā)組件狀態(tài)的更新
  • 用處:有狀態(tài)組件被大量使用在業(yè)務(wù)邏輯開(kāi)發(fā)中

組件的創(chuàng)建

  • ES5 的 React.createClass 方式,逐漸被下面的 ES6 代替
  • ES6 的 React.Component 方式
import React, { Component } from 'react';

class TextView extends Component {
    constructor(props) {
        super(props);
        this.state = {};
    }

    render() {
        return (
            <div>Text</div>
        );
    }
}
  • 無(wú)狀態(tài)的函數(shù)組件方式(箭頭函數(shù)格式)
const Todo = (props) => {
    <li 
        onClick={props.onClick}
        style={{textDecoration: props.complete ? "line-through" : "none"}}>
        {props.text}
    </li>
}
const Todo = ({ onClick, complete, text, props }) => {
    <li 
        onClick={props.onClick}
        style={{textDecoration: props.complete ? "line-through" : "none"}}>
        {props.text}
    </li>
}

【注意】

  • 無(wú)狀態(tài)組件一般會(huì)搭配高階組件(OHC)一起使用
  • 高階組件主要用來(lái)托管 state
  • Redux 框架就是通過(guò) store 來(lái)管理數(shù)據(jù)源和組件的所有狀態(tài)狮暑,其中所有負(fù)責(zé)展示的組件都使用無(wú)狀態(tài)函數(shù)式寫(xiě)法
  • 無(wú)狀態(tài)組件被大規(guī)模應(yīng)用在大型應(yīng)用程序中
  • 缺點(diǎn):無(wú)狀態(tài)組件在被 React 調(diào)用之前鸡挠,組件還沒(méi)有實(shí)例化,所以它不支持 ref 特性

2.2 props

  • props 是組件對(duì)外的接口搬男,一般情況下拣展,props是不變的
  • state 是組件對(duì)內(nèi)的接口

props 的使用方式

{this.props.key}

props 是父子組件交互的唯一方式:super(props);

const { Component } = require("react");

class HelloMessage extends Component {
    constructor(props) {
        super(props);
        this.state = {
            name = 'Jack'
        };
    }

    render() {
        return (
            <h1>Hello {this.props.name}</h1>
        )
    }
}

export default HelloMessage;
  • 通過(guò) {this.props.name} 方式獲取 props 中的值
  • ES5的語(yǔ)法中,通過(guò) getDefaultProps() 方法中設(shè)置默認(rèn)值

在子類中定義props

const { Component } = require("react");

export default class Child extends Component {
    constructor(props) {
        super(props);
        this.state = {
            counter:props.age||0
        };
    }

    render() {
        return (
            <h1>Hello {this.props.name}</h1>
        )
    }
}

Child.PropTypes = {
    name: PropTypes.string.isRequired,
    age: PropTypes.number
}

Child.defaultProps = {
    age: 0
}

在父類中使用 props

export default class Father extends Comment {
    render() {
        return (
            <div>
                <Child name="Jack" age={20} />
                <Child name="Tom" age={30} />
            </div>
        )
    }
}
  • 如果父類需要向子組件傳遞數(shù)據(jù)缔逛,只需要在組件中引入子組件备埃,然后使用組件提供的props屬性,即可向子組件傳遞數(shù)據(jù)
  • 子組件 props 接受的數(shù)據(jù)格式由 PropTypes 進(jìn)行檢測(cè)

2.3 state

  • props 是組件對(duì)外的接口褐奴,一般情況下按脚,props是不變的,props對(duì)使用它的組件來(lái)說(shuō)是只讀的敦冬,如果要修改props辅搬,只能通過(guò)組件的父類組件修改
  • state 是組件對(duì)內(nèi)的接口,是組件的私有屬性脖旱,只能被本組件訪問(wèn)和修改

組件“狀態(tài)機(jī)”

通過(guò)與用戶交互實(shí)現(xiàn)不同狀態(tài)堪遂,進(jìn)而渲染界面,讓用戶界面與數(shù)據(jù)保持一致

在 React 中萌庆,如果需要使用 state蚤氏,就需要在組件的 constructor 中初始化相關(guān)的 state

constructor(props) {
    super(props);
    this.state() = {
        key:value,
        ...
    };
}

setState():更新組件的state

this.setState({
    key:value,
});

setState() 異步操作

  1. 更新的狀態(tài)不會(huì)立馬刷新,而是將修改的狀態(tài)放入一個(gè)隊(duì)列中踊兜;
  2. React 可能會(huì)對(duì)多次 setState 狀態(tài)修改進(jìn)行合并修正竿滨;
  3. {this.state} 獲取的狀態(tài)可能會(huì)不準(zhǔn)確;
  4. 也不能依賴 props 來(lái)計(jì)算組件的下一個(gè)狀態(tài)捏境;

setState 淺合并過(guò)程

在調(diào)用 setState 修改組件狀態(tài)時(shí)于游,只需要傳入需要改變的狀態(tài)變量即可,不需要傳入組件完整的 state

title 和 content 屬性

this.state = {
    title: 'Jack',
    content: 'Welcome to React',
}

當(dāng)只需要修改 title 屬性時(shí)垫言,只在 setState() 中修改 title 即可

this.setState({
    title: 'Tom',
});
  • 修改了title
  • content 保持原有狀態(tài)

淺合并之后的結(jié)果是

{
    title: 'Tom',
    content: 'Welcome to React',
}

2.4 ref

  • 本質(zhì)就是調(diào)用 ReactDOM.render()返回的組件實(shí)例贰剥,用來(lái)表示對(duì)組件真正實(shí)例的引用。
  • 具體使用時(shí)筷频,可以將它綁定到組件的 render() 上蚌成,然后就可以用它輸出組件的實(shí)例前痘。
  • 可以使用 ref 方式來(lái)修改子組件。
  • ref 不僅可以掛在到組件上担忧,還可以作用于具體的 DOM 元素芹缔。掛載到 DOM 元素時(shí),ref 可以表示具體的 DOM 元素節(jié)點(diǎn)瓶盛。
  • ref 表示對(duì)組件實(shí)例的引用時(shí)最欠,不能再函數(shù)式組件內(nèi)上使用 ref 屬性。
  • ref 調(diào)用方式:設(shè)置回調(diào)函數(shù)字符串的方式惩猫,官方推薦回調(diào)函數(shù)芝硬。

ref 調(diào)用方式:回調(diào)函數(shù)

class Demo extends Component {
    constructor(props) {
        super(props);
        this.state = {
            isInputShow: false  // 控制 input 是否渲染
        };
    }

    inputRefcb(instance) {
        if (instance) {
            instance.focus();
        }
    }

    render() {
        {
            this.state.isInputShow ?
            <div>
                <input ref={this.inputRefcb} type="text"/>
            </div>
            : null
        }
    }
}

觸發(fā)回調(diào)函數(shù)的時(shí)機(jī)

  • 組件被渲染后,回調(diào)參數(shù) instance 作為 input 的組件實(shí)例的引用轧房,可以立即使用該組件
  • 組件被卸載后拌阴,回調(diào)參數(shù) instance 此時(shí)為 null,這樣可以確保內(nèi)存不被泄露
  • ref 屬性本身發(fā)生改變奶镶,原有的 ref 會(huì)再次被調(diào)用皮官,此時(shí)的回調(diào)參數(shù)instance 變成具體的組件實(shí)例

ref 調(diào)用方式:字符串

class Demo extends Component {
    constructor(props) {
        super(props);
    }

    onfocus() {
        this.refs.inputRef.focus()
    }
    
    render() {
        {
            this.state.isInputShow ?
            <div>
                <input ref="inputRef" type="text"/>
                <input type="button" value="Focus" onClick={this.onfocus}/>
            </div>
            : null
        }
    }
}
  • 通過(guò) this.refs.inputRef 來(lái)獲取組件實(shí)例
  • 不能在函數(shù)式聲明組件中使用 ref,因?yàn)樗麄儾荒塬@取組件的實(shí)例

父組件訪問(wèn)子組件的 DOM 節(jié)點(diǎn)

function TextInput(props) {
    return (
        <div>
            <input ref={props.inputRef} />
        </div>
    )
}

class Father extends Component {
    render() {
        return (
            <TextInput inputRef={
                e => this.inputElement = e
            } />
        );
    }
}

訪問(wèn)過(guò)程:

  1. 在父組件 Father 中引用子組件 TextInput
  2. 子組件 TextInput 通過(guò) ref 傳入 inputRef 函數(shù)
  3. 子組件 TextInput 又將這個(gè)回調(diào)函數(shù)作為 input 元素的 ref 屬性
  4. 父組件 Father 可以通過(guò) {this.inputElement} 獲取子組件的 input 對(duì)應(yīng)的 DOM 元素实辑。

(三)React 高階組件

3.1 定義與實(shí)現(xiàn)

  • 高階組件是接受 React組件 作為參數(shù),并返回一個(gè)新的 React組件 的組件藻丢。
  • 高階組件可以看做是對(duì)傳入的 React組件 經(jīng)過(guò)一系列處理剪撬,最后返回一個(gè)相對(duì)增強(qiáng)的 React組件
  • 高階組件本質(zhì)上一個(gè)函數(shù),不是組件悠反,可以參考鏈?zhǔn)骄幊?/code>

編寫(xiě)一個(gè)高階組件

  • 接受一個(gè) WrappedComponent 組件
  • 返回一個(gè) HOC 的 withHeader 組件
import React, {Component} from 'react';

export default function withHeader(WrappedComponent) {

    return class HOC extends Component {
        render() {
            return <div>
                <div className=" header">我是標(biāo)題</div>
            </div>
        }
    }
}

高階組件也可以作為一個(gè)普通組件使用

@withHeader
export default class Demo extends Component {
    render() {
        return (
            <div>我是一個(gè)普通組件</div>
        );
    }
}

@withHeader 是 ES7 中的裝飾器語(yǔ)法残黑,相當(dāng)于下面的表達(dá)式

const EnhanceDemo = withHeader(Demo);

如果在某個(gè)組件中多次重復(fù)使用同一個(gè)高階組件,在調(diào)試時(shí)就會(huì)看到一大堆相同的高階組件斋否,可以在使用時(shí)保留高階組的原有名稱來(lái)區(qū)分梨水。

3.2 分類

高階組件的實(shí)現(xiàn)方式:屬性代理反向繼承

  • 屬性代理:通過(guò)返回包括原組件并添加額外功能來(lái)實(shí)現(xiàn)高階組件,最常見(jiàn)
const Container = (WrappedComponent) => class extends Components {
    render() {
        const newProps = {
            text: 'newText',
        }
        return <WrappedComponent> {...this.props} {...newProps} />
    }
}
  • 反向繼承:通過(guò)返回繼承原組件來(lái)控制 render 函數(shù)茵臭,進(jìn)而實(shí)現(xiàn)高階組件疫诽。相對(duì)于屬性代理,反向繼承能訪問(wèn)到的區(qū)域和權(quán)限更多
const Container = (WrappedComponent) => class extends WrappedComponent {
    render() {
        return super.render();
    }

通過(guò)繼承 WrappedComponent旦委,可以使用 WrappedComponent 組件的 state奇徒、props、生命周期缨硝、render()等

3.3 命名與參數(shù)

displayName屬性:當(dāng)高階組件包裹普通組件時(shí)摩钙,普通組件的名稱和靜態(tài)方法都會(huì)丟失,為了避免這種情況妒牙,給普通組件添加標(biāo)組組件名稱的 displayName屬性

class HOC extends ...{
    static displayName = `HOC(${getDisplayName(WrappedComponent)})`;
}

// getDisplayName 方法
function getDisplayName(WrappedComponent) {
    return WrappedComponent.displayName ||
            WrappedComponent.name ||
            'Component';
}

柯里化:通過(guò)傳入不同的參數(shù)來(lái)得到不同的高階組件

function HOCFactoryFactory(...params) {
    // 通過(guò)改變 params 來(lái)顯示不同結(jié)果
    return class HOCFactory(WrappedComponent) {
        render() {
            return <WrappedComponent {...this.props} />
        }
    }
}

高階組件的缺點(diǎn)

可能會(huì)造成靜態(tài)方法丟失和 ref 屬性不能傳遞徒爹,所以在使用過(guò)程中需要遵循一下準(zhǔn)則

  • 不要在組件的 render() 中使用高階組件,也盡量避免在組件的其他生命周期方法中使用高階組件
  • 如果需要使用被包裝組件的靜態(tài)方法竣稽,那么必須手動(dòng)賦值靜態(tài)方法
  • ref 應(yīng)避免傳遞給包裝組件

(四)組件通信

4.1 父子組件通信

父組件 --> 子組件

父組件通過(guò) props 將值傳遞給子組件

  • 父組件傳值:params={this.state.params}
class Parent extends Component {
    state = {
        params: 'father send msg to child'
    };
    render() {
        return <Child params={this.state.params} />;
    }
}
  • 子組件接收值:{this.props.params}
class Child extends Component { 
    render() {
        return <p>{this.props.params}</p>
    }
}

子組件 --> 父組件

  • 回調(diào)函數(shù)【最常見(jiàn)】
  • 自定義事件

回調(diào)函數(shù)方式:

  1. 父組件將一個(gè)函數(shù)作為 props 傳遞給子組件长踊;
  2. 組組件調(diào)用該回調(diào)函數(shù)便可以向父組件傳值功舀;
class Parent extends Component {
    constructor (props) {
        super(props);
        this.state = {
            params: 'child send msg to father'
        };
    }
    
    transMsg(types) {
        console.log(type);
    }
    
    render() {
        return <Child params={this.state.params} />;
    }
}

class Child extends Component {
    constructor(props) {
        super(props);
        console.log("params :", this.props.params);
        this.props.transMsg("hi, fathre");
    }
    
    render() {
        return <p>{this.state.params}</p>
    }
}

4.2 跨級(jí)組件通信

父組件與子組件的子組件或者是更深層的子組件進(jìn)行的通信。實(shí)現(xiàn)方式有兩種:

  • 使用組件 props 逐層傳遞
  • 使用 context 對(duì)象傳遞

使用組件 props 逐層傳遞 的缺點(diǎn)

  • 每一層都需要傳遞 props
  • 增加程序的復(fù)雜度
  • 實(shí)際開(kāi)發(fā)中不建議使用

使用 context 對(duì)象傳遞

  • 父組件需要聲明支持 context之斯,并提供一個(gè)函數(shù)來(lái)返回相應(yīng)的 context 對(duì)象
  • 子組件聲明需要使用的 context 對(duì)象日杈,并提供需要使用的 context 屬性的 PropTypes

代碼

export default class GrandSon extends Component {
    // 子組件聲明自己需要使用的 context
    static contextTypes = {
        color : PropTypes.string,
    };
    static propTypes = {
        value : PropTypes.string,
    };

    render() {
        const { value } = this.props;
        return (
            <li style={{background: this.context.color}}>
                <span>{value}</span>
            </li>
        );
    }
}

export default class Father extends Component {
    // 聲明自己要使用的 context
    static fatherContextTypes = {
        color : PropTypes.string,
    };
    static propTypes = {
        name : PropTypes.string,
    };

    // 提供一個(gè)函數(shù), 用來(lái)返回 context 對(duì)象
    getFatherContext() {
        return {
            color : 'red',
        };
    }
    render () {
        const {list} = this.props;
        return (
            <div>
                <ul>
                    {
                        list.map((entry, index) =>
                            <GrandSon key={`list-${index}`} value={entry.text} />
                        )
                    }
                </ul>
            </div>
        );
    }
}

class GrandFather extends Component {
    render() {
        return(
            <div>
                <Father name='GrandFather'/>
            </div>
        );
    }
}

如果組件中使用了構(gòu)造函數(shù),為了不影響跨級(jí)組件通信佑刷,還需要在構(gòu)造函數(shù)中傳入第二個(gè)參數(shù) context

constructor(props, context) {
    super(props, context);
}

context 的缺點(diǎn)

因?yàn)閏ontext可以代表任何東西莉擒,所以它的類型是無(wú)法確定的,所以在使用的過(guò)程中也是需要謹(jǐn)慎對(duì)待

【總結(jié)】在父子組件通信模型中

  • 父組件 -> 子組件:使用變量
  • 子組件 -> 父組件:父組件提供回調(diào)函數(shù)瘫絮,子組件調(diào)用回調(diào)函數(shù)

其實(shí)如果將回調(diào)函數(shù)也看成一個(gè)屬性涨冀,那么這兩個(gè)過(guò)程其實(shí)都是一樣的,都是子組件使用父組件提供的“屬性”(變量或回調(diào)函數(shù))

4.3 非嵌套組件通信

沒(méi)有直接關(guān)系的兩個(gè)組件麦萤,例如兄弟組件(同一個(gè)父節(jié)點(diǎn)下的兩個(gè)節(jié)點(diǎn))鹿鳖、完全不相干的兩個(gè)組件。

對(duì)于兄弟組件壮莹,也是不可以直接通信的翅帜,可以通過(guò)狀態(tài)提升來(lái)實(shí)現(xiàn)兄弟組件間的通信。提升狀態(tài)就是值通過(guò)父組件進(jìn)行中轉(zhuǎn)命满,但是當(dāng)層級(jí)較深時(shí)涝滴,中轉(zhuǎn)過(guò)程也會(huì)特別復(fù)雜,如何尋找公共父組件也是一個(gè)問(wèn)題胶台。

自定義事件

  • 發(fā)布/訂閱模型
  • 給事件對(duì)象上添加監(jiān)聽(tīng)器
  • 通過(guò)觸發(fā)事件來(lái)實(shí)現(xiàn)組件之間的通信
  • 在組件的 componentDidMount 中聲明自定義事件
  • 在組件的 componentWillUnmount 中取消訂閱事件

安裝 events 模塊

通過(guò)自定義事件的方式來(lái)實(shí)現(xiàn)非嵌套組件間的通信歼疮,需要借助Node的events模塊,通過(guò)以下命令安裝 events 模塊

npm install events --save

然后在 src 目錄下創(chuàng)建一個(gè) events.js 文件

import { EventEmitter } from 'events';
export default new EventEmitter();

再創(chuàng)建一個(gè) ComponentA.js 文件

  • 在組件ComponentA.jscomponentDidMount 中聲明自定義事件
  • 在組件ComponentA.jscomponentWillUnmount 中取消訂閱事件
export default class ComponentA extends Component {
    constructor(props) {
        super(props);
        this.state = {
            message : 'ComponentA',
        };
    }

    // 聲明一個(gè)自定義事件
    componentDidMount() {
        this.eventEmitter = events.addListener('changeMessage', (message) => {
            this.setState ({
                message,
            });
        });
    }

    // 取消事件訂閱
    componentWillUnmount() {
        events.removeListener(this.eventEmitter);
    }

    render() {
        return (
            <div>
                {this.state.message}
            </div>
        );
    }
}

再創(chuàng)建一個(gè)組件ComponentB

  • 添加一個(gè)點(diǎn)擊事件
  • ComponentA 接收到 ComponentB 發(fā)過(guò)來(lái)的消息后诈唬,刷新界面
// 組件 ComponentB
export default class ComponentB extends Component {
    handleClick = (message) => {
        events.emit('changeMessage', message);
    };
    render() {
        return(
            <div>
                <button onClick={this.handleClick.bind(this, 'ComponentB')}>點(diǎn)擊發(fā)送信息</button>
            </div>
        );
    }
}

創(chuàng)建測(cè)試用例韩脏,模擬兩個(gè)非嵌套組件的通信

// 測(cè)試用例
export default class AppTest extends Component {
    render() {
        return (
            <div>
                <ComponentA />
                <ComponentB />
            </div>
        );
    }
}

【總結(jié)】原生通知了解下

  • 組件 ComponentA 監(jiān)聽(tīng)通知
  • 組件 ComponentB 發(fā)送通知

(五)事件處理

5.1 事件監(jiān)聽(tīng)與處理

React 事件 和 HTML 事件

  • React 事件使用駝峰命名法,而非全部小寫(xiě)
  • React中铸磅,可以傳遞一個(gè)函數(shù)作為事件的處理函數(shù)赡矢,而非一個(gè)簡(jiǎn)單的字符串

為按鈕添加一個(gè)事件

  • 只需要給 React 元素添加 onClick、onKeyDown 函數(shù)即可
class demo extends Component {
    handleClick() {
        console.log('Click me')
    }

    render() {
        return(
            <button onClick={this.handleClick}>React 實(shí)戰(zhàn)</button>
        );
    }
}

事件攔截

HTML 中通過(guò) return false 來(lái)攔截事件

<a href="#" onclick="console.log('The link was clicked"); return false">
    Click me
</a>

React 使用虛擬DOM基礎(chǔ)上實(shí)現(xiàn)的合成事件 SyntheicEvent

  • React 的時(shí)間處理程序接受的是 SyntheicEvent實(shí)例
  • stopPrepagation():阻止時(shí)間傳遞阅仔,目的是不讓事件分派到其他的 Document 節(jié)點(diǎn)济竹,但是默認(rèn)事件依然會(huì)執(zhí)行
  • preventDefault():通知瀏覽器不要執(zhí)行與事件關(guān)聯(lián)的默認(rèn)動(dòng)作,但事件依然會(huì)繼續(xù)傳遞霎槐。
function ActionLink() {
    function handleClick(e) {
        e.prevenDefault();
    }

    return (
        <a href="#" onClick={handleClick}>Click me</a>
    );
}

5.2 event 事件與 this 關(guān)鍵字

event 事件

  1. React 在虛擬 DOM 的基礎(chǔ)上實(shí)現(xiàn)的一套合成事件
  2. 處理監(jiān)聽(tīng)時(shí)送浊,需要傳入一個(gè) event 對(duì)象
  3. 完全符合 W3C 標(biāo)準(zhǔn),所以可以完全兼容瀏覽器丘跌,并擁有和瀏覽器一樣的事件接口

案例一:輸出按鈕的 innerHTML

class Demo extends Component {
    handleClick(e) {
        console.log(e.target.innerHTML)
    }

    render() {
        return(
            <button onClick={this.handleClick}>React 實(shí)戰(zhàn)</button>
        );
    }
}

函數(shù)與對(duì)象方法

先來(lái)看一個(gè)例子袭景,在上述方法中唁桩,如果輸出 this,this結(jié)果是 null 或者 undefined

handleClick(e) {
        console.log(this)   // `null` 或者 `undefined`
    }

原因handleClick 是一個(gè)函數(shù)耸棒,并非是通過(guò)對(duì)象的方法調(diào)用的荒澡,而是直接的函數(shù)調(diào)用,所以在這個(gè)函數(shù)中与殃,就無(wú)法獲取到 this 所代表的類實(shí)例

解決辦法:將函數(shù)綁定到當(dāng)前實(shí)例上

render() {
        return(
            <button onClick={this.handleClick.bind(this)}>React 實(shí)戰(zhàn)</button>
        );
    }

bind方法

  1. bind方式實(shí)現(xiàn)時(shí)間監(jiān)聽(tīng)非常常見(jiàn)单山;
  2. bind是React在ES5引入的事件監(jiān)聽(tīng)機(jī)制;
  3. bind格式:Function.prototype.bind()

bind原理

  1. 當(dāng)調(diào)用函數(shù)對(duì)象的 bind() 方法時(shí)
  2. 系統(tǒng)會(huì)重新創(chuàng)建一個(gè)函數(shù)幅疼,新函數(shù)的行為和原函數(shù)一樣
  3. 因?yàn)樗麄兪怯芍付ǖ?this 和初始化參數(shù)構(gòu)造的原函數(shù)

bind傳參

  • 格式:bind(this, arg1, arg2, ...)
function f() {
    return this.a;
}
var g = f.bind({a:"azertyp"});
console.log(g());   //azertyp
var h = g.bind({a:"yoo"});  // bind 只生效一次
console.log(h());   //azertyp

5.3 EventEmitter 在 React Native 中的應(yīng)用

EventEmitter 是用來(lái)處理原生和 React Native 之間通信的

iOS原生和 JavaScript 層交互關(guān)系表

原始端函數(shù) JavaScript 層接口
sendEventWithName RCTAppEventEmitter
sendDeviceEventWithName RCTDeviceEventEmitter
sendInputEventWithName RCTInputEventEmitter
  • iOS 中使用 RCTEventEmitter
  • Android 中使用 RCTDeviceEventEmitter

iOS 和 React Native 交互

iOS 中通過(guò) eventDispatchersendAppEventWithName 方法將消息傳遞個(gè) JavaScript

#import "CalendarManager.h"
#import "RCTEventDispatcher.h"

@implementation CalendarManager

@synchesize bridge=_bridge;

-(void)calendarEventReminderReceived:(NSNotification *)notification {
    NSString* eventName = notification.userInfo[@"name"];
    [self.bridge.eventDispatcher sendAppEventWithName"EventReminder" body:@{@"name" : eventName}];
}

@end

JavaScript 通過(guò) addListener 訂閱該事件米奸,注意保持 name 一致,iOS中發(fā)出來(lái)的name是 EventReminder爽篷,所以addListener監(jiān)聽(tīng)的也應(yīng)該是 EventReminder悴晰。

在事件使用完之后取消事件的訂閱,即在 conponentWillUnmount 聲明周期函數(shù)中取消事件的訂閱逐工。

import { NativeAppEventEmitter, NativeEventEmitter } from 'react-native';

var subscription = NativeEventEmitter.addListener(
    'EventReminder', (reminder) => console.log(reminder.name)
);
...
// 取消訂閱事件
conponentWillUnmount() {
    subscription.remove();
}

Android 和 React Native 交互

Android中铡溪,通過(guò) RCTDeviceEventEmitter 來(lái)注冊(cè)事件

getReactApplicationContext()
    .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
    .emit("EventReminder", null);

(六)React Hook

6.1 Hook 簡(jiǎn)介

  1. React Hook 是為了解決 React 的狀態(tài)共享問(wèn)題;
  2. 狀態(tài)共享也可以看成是邏輯復(fù)用的問(wèn)題泪喊;
  3. 因?yàn)?React Hook 治共享數(shù)據(jù)處理邏輯棕硫,并不會(huì)共享數(shù)據(jù)本身;

在 React 應(yīng)用開(kāi)發(fā)中袒啼,狀態(tài)管理是組價(jià)你開(kāi)發(fā)必不可少的內(nèi)容哈扮。狀態(tài)管理的方式:

  • 使用類組件
  • 使用 redux 等狀態(tài)管理框架

案例【以前的做法】

class Example extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count : 0
        };
    }    

    render() {
        return (
            <div>
                <p>You clicked {this.state.count} times</p>
                <button onClick={()=>this.state({ count: this.state.count+1 })}>Click me</button>
            </div>
        );
    }
}

案例【現(xiàn)在的做法】

  • 使用 React Hook 提供的 State Hook 來(lái)處理狀態(tài);
  • 針對(duì)已經(jīng)存在的類組件瘤泪,也可以使用 State Hook 很好的進(jìn)行重構(gòu);
  • Example 變成了一個(gè)函數(shù)組件育八,有自己的狀態(tài)对途,還可以更新自己的狀態(tài);
  • useState函數(shù) 是 React 的一個(gè) hook 函數(shù)髓棋,它的作用是聲明狀態(tài)變量实檀。
function Hook() {
    const [count, setCount] = useState(0);
    return (
        <div>
            <p>You clicked {this.state.count} times</p>
            <button onClick={()=>this.state({ count: this.state.count+1 })}>Click me</button>
        </div>
    );
}

6.2 Hook API

Hook API 背景故事

1. 如何解決狀態(tài)組件的復(fù)用問(wèn)題?

一般都是通過(guò)自上而下傳遞的數(shù)據(jù)流來(lái)將大型的視圖拆分成獨(dú)立的可復(fù)用組件按声。但是在實(shí)際開(kāi)發(fā)中膳犹,如何復(fù)用一個(gè)帶有業(yè)務(wù)邏輯的組件讓讓是一個(gè)問(wèn)題。

2. 函數(shù)組件和類組件

前面介紹了他們的一些特性:函數(shù)組件缺少組件的 狀態(tài)签则、生命周期等特征须床,所以一直不受青睞

但是 Hool API 賦予了函數(shù)組件這些能力

React 提供了三個(gè)核心的 API

  • State API:狀態(tài)API
  • Effect API:聲明周期API
  • Custom API:自定義API

useState 組件

用來(lái)定義和管理本地狀態(tài)

下面看一個(gè)計(jì)數(shù)器的小案例

  • 函數(shù)組件的對(duì)象也可以是基礎(chǔ)類型值
  • useState 返回的是一個(gè)數(shù)組,數(shù)組的第一個(gè)對(duì)象表示當(dāng)前狀態(tài)的值渐裂,第二個(gè)對(duì)象表示用于更改狀態(tài)的函數(shù)豺旬,類似于類組件的 setState
function App() {
    const [count, setCount] = useState(0);
    return (
        <div>
            <button onClick={ () => setCount(count+1)}>+</button>
            <span>{count}</span>
            <button onClick={ () => setCount(count-1)}>-</button>
        </div>
    )
}

export default App;

useState 的聲明方式

  • 單次聲明多個(gè)對(duì)象
const [count, setCount] = useState({
    count1: 0,
    coutn2: 0
});
  • 多次聲明多個(gè)對(duì)象
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);

在實(shí)際使用中钠惩,多次聲明會(huì)更加方便,因?yàn)楦潞瘮?shù)采用的是替換而不是合并族阅。

如果要處理嵌套多層的數(shù)據(jù)邏輯篓跛,用 useState 就顯得力不從心了,需要使用 React 提供的 useReducer 來(lái)處理這類問(wèn)題

useReducer 的用法

import React, {useReducer} from 'react';

console reducer = function(state, action) {
    switch (action.type) {
        case "increment":   // 增加
            return {count: state.count+1};
        case "decrement":   // 減少
            return {count: state.count-1};
        default
            return {count: state.count};    
    }
};

function Example() {
    const [state, dispatch] = useReducer(reducer, {count:0});
    const {count} = state;
    return (
        <div>
            <button onClick={ () => dispatch({type: "increment"})}>+</button>
            <span>{count}</span>
            <button onClick={ () => dispatch({type: "decrement"})}>-</button>
        </div>
    );
}

export default Example;
  • useReducer 接受 reducer 函數(shù)和默認(rèn)值兩個(gè)參數(shù)
  • 返回當(dāng)前狀態(tài) state 和 dispatch 函數(shù)的數(shù)組
  • 使用方式與 Redux 框架一致
  • useReducer 沒(méi)有采用 Redux 方式設(shè)置默認(rèn)值坦刀,是因?yàn)镽eact認(rèn)為狀態(tài)的默認(rèn)值可能來(lái)自于函數(shù)組件的props
function Example({initialState = 0}) {
    const {state, dispatch} = useReducer(reducer, { count: initialState} );
    ...
}

Effct Hook 管理聲明周期

import React, {useState, useEffect} from 'react';


function Example() {
    const [count, setCount]= useState(0);

    useEffect( () => {
        console.log('componentDidMount...');
        console.log('componentDidUpdate...');
        return() => {
            console.log('componentWillUnmount...');
        }
    }
        
    );

    return (
        <div>
            <button onClick={ () => setCount(count+1)}>
                Click me
            </button>
        </div>
    );
}

export default Example;

每次點(diǎn)擊按鈕的時(shí)候愧沟,輸出的內(nèi)容為

componentDidMount...
componentDidUpdate...
componentWillUnmount...
componentDidMount...
componentDidUpdate...
...
  • 每次執(zhí)行組件更新時(shí),useEffect 中的回到函數(shù)都會(huì)被調(diào)用鲤遥;
  • 在重新繪制前執(zhí)行銷毀操作沐寺,避免造成內(nèi)存泄漏;
  • useEffect 可以被視為 componentDidMount, componentDidUpdate, componentWillUnmount 的數(shù)組渴频,并用它關(guān)聯(lián)函數(shù)組件的生命周期
  • 類組件的 componentDidMount, componentDidUpdate 聲明周期函數(shù)都是在 DOM 更新后同步執(zhí)行的
  • useEffect 不是同步執(zhí)行的芽丹,不會(huì)阻塞瀏覽器更新界面
  • 需要模擬生命周期的同步執(zhí)行,可以使用 React 提供的 useLayoutEffect Hook

6.3 自定義 Hook

  1. 自定義 Hook:函數(shù)名是以 use 開(kāi)頭的并調(diào)用其他 Hook 的封裝函數(shù)
  2. 自定義 Hook 的每個(gè)狀態(tài)都是獨(dú)立的

使用 axios 實(shí)現(xiàn)一個(gè)自定義 Hook 的案例

import React, {useState, useEffect} from 'react';
import axios from 'axios';

export const useAxios = (url, dependecies) => {
    const [isLoading, setIsLoading] = useState(false);
    const [response, setReponse] = useState(null);
    const [error, setError] = useState(null);

    useEffect(() => {
        setIsLoading(true);
        axios.get(Url).then((res) => {
            setIsLoading(false);
            setReponse(res);
        }).catch((err) => {
            setIsLoading(false);
            setError(err);
        });
    }, dependecies);

    return [isLoading, response, error];
};

在 Example 中使用 axois 自定義 Hook 函數(shù)組件

import React, {useState, useEffect} from 'react';
import axios from 'axios';

export const useAxios = (url, dependecies) => {
    const [isLoading, setIsLoading] = useState(false);
    const [response, setReponse] = useState(null);
    const [error, setError] = useState(null);

    useEffect(() => {
        setIsLoading(true);
        axios.get(Url).then((res) => {
            setIsLoading(false);
            setReponse(res);
        }).catch((err) => {
            setIsLoading(false);
            setError(err);
        });
    }, dependecies);

    return [isLoading, response, error];
};

function Example() {
    let url = 'http://api.douban.com/v2/movie/in_theaters';
    const [isLoading, response, error] = useAxios(url, []);

    return (
        <div>
            {isLoading ? <div>Loading...</div> :
            (error ? <div> There is an error happned</div> : <div>Success {response}</div>
        }
        </div>
    );
}
 export default Example;

自定義 Hook 的優(yōu)勢(shì)

  1. 簡(jiǎn)潔易讀
  2. 不會(huì)引起組件嵌套的問(wèn)題

自定義 Hook 使用的注意事項(xiàng)

  1. 不要在循環(huán)卜朗、條件或嵌套函數(shù)中使用 Hook拔第,并且只能在 React 函數(shù)的頂層使用 Hook,這是因?yàn)?React需要利用調(diào)用順序來(lái)正確更新相應(yīng)的狀態(tài)场钉,以及調(diào)用相應(yīng)的生命周期函數(shù)蚊俺。一旦在循環(huán)或條件分支語(yǔ)句中調(diào)用 Hook,就容易引起調(diào)用順序不一致,產(chǎn)生難以預(yù)料的后果
  2. 只能在 React 函數(shù)式組件或自定義Hook中使用 Hook逛万。

eslint

避免在開(kāi)發(fā)中引起低級(jí)錯(cuò)誤,可以在項(xiàng)目中安裝一個(gè) eslint 插件

yarn add eslint-plugin-react-hooks --dev

然后在eslint的配置文件中添加如下配置:

{
    "plugins" : [
        // ...
        "react-hooks"
    ],
    "rules" : [
        // ...
        "react-hooks/rules-of-hooks" : "error",
        "react-hooks/exhaustive-deps" : "warn",
    ]
}

借助 React 提供的 Hook API泳猬,函數(shù)組件可以實(shí)現(xiàn)絕大部分類組件功能,并且 Hook 在共享狀態(tài)邏輯宇植、提高組件復(fù)用性上也有一定的優(yōu)勢(shì)得封。可以預(yù)見(jiàn)的是指郁,Hook將是 React 未來(lái)發(fā)展的重要方向忙上。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市闲坎,隨后出現(xiàn)的幾起案子疫粥,更是在濱河造成了極大的恐慌,老刑警劉巖腰懂,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件梗逮,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡绣溜,警方通過(guò)查閱死者的電腦和手機(jī)慷彤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人瞬欧,你說(shuō)我怎么就攤上這事贷屎。” “怎么了艘虎?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵唉侄,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我野建,道長(zhǎng)属划,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任候生,我火速辦了婚禮同眯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘唯鸭。我一直安慰自己须蜗,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布目溉。 她就那樣靜靜地躺著明肮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缭付。 梳的紋絲不亂的頭發(fā)上柿估,一...
    開(kāi)封第一講書(shū)人閱讀 51,554評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音陷猫,去河邊找鬼秫舌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛绣檬,可吹牛的內(nèi)容都是我干的足陨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼娇未,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼墨缘!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起忘蟹,我...
    開(kāi)封第一講書(shū)人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤飒房,失蹤者是張志新(化名)和其女友劉穎搁凸,沒(méi)想到半個(gè)月后媚值,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡护糖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年褥芒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡锰扶,死狀恐怖献酗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坷牛,我是刑警寧澤罕偎,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站京闰,受9級(jí)特大地震影響颜及,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蹂楣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一俏站、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧痊土,春花似錦肄扎、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至赞哗,卻和暖如春雷则,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肪笋。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工月劈, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人藤乙。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓猜揪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親坛梁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子而姐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355