React 系統(tǒng)性學(xué)習(xí)(上)

$ 前言

? 最近在考慮框架轉(zhuǎn)型,鑒于作為一名JSer钓辆,要時(shí)時(shí)刻刻保持對新技術(shù)和流行技術(shù)的敏感性泪幌,而 React呀非、Vue、Angular 已基本占領(lǐng)現(xiàn)前端市場禽作,React作為領(lǐng)頭大哥尸昧,建議年輕的JSer們都要學(xué)習(xí)使用或至少了解這門技術(shù)。

$ 版本聲明

? 本文使用版本 React v16.2.0

$ 什么是 React ?

? React是一個(gè)聲明式的旷偿,高效的烹俗,并且靈活的用于構(gòu)建用戶界面的 JavaScript 庫

? 一個(gè)最簡單的React例子

ReactDom.render(
    <h1>Hello World</h1>,
    document.getElementById('root')
)

? ReactDom.render接受兩個(gè)參數(shù),第一個(gè)是要被插入的內(nèi)容萍程,第二個(gè)是插入到DOM或者說index.html的位置
?

$ 一個(gè)與Html對比的簡單組件

? 如下是一個(gè) React 組件

class ShoppingList extends React.Componnet {
    render() {
        return (
            <div className="shopping-list">
                <h1>Shoping List for {this.props.name}</h1>
                <ul>
                    <li>Instagram</li>
                    <li>WhatApp</li>
                    <li>Oculus</li>
                </ul>
            </div>
        )
    }
}

// Example usage:  <ShoppingList name="Mark" />

? 在這里幢妄,ShoppingList是一個(gè) React組件類,或 React組件類型茫负。組件接受參數(shù)蕉鸳,稱為屬性 props, 并通過 render方法返回一個(gè)現(xiàn)實(shí)的視圖層次結(jié)構(gòu)。

? render 方法返回您要渲染的內(nèi)容描述,然后React接受該描述并將其渲染到屏幕上潮尝,特別是榕吼,render 返回一個(gè)React 元素,這是一個(gè)渲染內(nèi)容的輕量級的描述勉失。大多數(shù)
React 開發(fā)人員使用 JSX 語法羹蚣,也是上述案例寫到的語法。

? JSX 語法的轉(zhuǎn)換規(guī)則為: <div /> 語法在構(gòu)建是被轉(zhuǎn)換為 React.createElement('div')乱凿。因此顽素,上面的例子等價(jià)于:

return React.createElement('div', {className: 'shopping-list'},
    React.createElement('h1', /* h1 children ... */),
    React.createElement('ul', /* ul children ... */)
);

? 既然 JSX 在 React 開發(fā)者中這么流行,那 JSX 又是什么呢徒蟆?
?

$ JSX 語法

? JSX 它是 Javascript 的一種拓展語法胁出,能夠讓你的 Javascript 中和正常描述 HTML一樣編寫 HTML。

? 你可以用 花括號 將任意 Javascript 表達(dá)式嵌入到 JSX 中后专。例如:表達(dá)式 1 + 2, 變量 user.firstName, 和函數(shù) formatName(User) 等都可以嵌入使用

function formatName(user) {
    return user.firstName + ' ' + user.lastName;
}

const user = {
    firstName: 'harper',
    lastName: 'Perez'
}

const element = {
    <h1> Hello, {formatName(user)}! </h1>
}

ReactDOM.render (
    element,
    document.getElementById('root')
)

? 請注意,為了方便閱讀開發(fā)者們常將 JSX分割成多行包裹起來输莺,因?yàn)檫@可以避免分號自動插入的陷阱戚哎,如

{ 1
2 } 3
// is transformed to
{ 1
;2 ;} 3;
JSX 也是一個(gè)表達(dá)式

? 編譯之后, JSX 表達(dá)式也就成了一個(gè)常規(guī)的 javascript 對象

? 也正因?yàn)槿绱松┯茫覀兛梢栽?if 語句或這是 for 循環(huán)語句中使用 JSX型凳,用它給變量賦值,當(dāng)做參數(shù)接受嘱函,或者作為函數(shù)的返回值

function getGreeting(user) {
    if (user) {
        return <h1>Hello. {formatName(User}</h1>;
    }
    return <h1>Hello, Stranger</h1>
}
用 JSX 指定屬性值

? 你可以用花括號嵌入一個(gè) JavaScript 表達(dá)式作為屬性值

// 用引號形式
const element = <div tableIndex="0"></div>;
// 用表達(dá)式甘畅,并且表達(dá)式用花括號包裹
const element = <img src={user.avatarUrl}></img>;
用 JSX 指定子元素

? 如果是空標(biāo)簽,可以直接用 /> 閉合

const element = <img src={user.avatarUrl} />

? 如果包含子標(biāo)簽:

<div>
    <h1>Hello!</h1>
    <h2>Good to see you here.</h2>
</div>

比起 HTML往弓,JSX 更接近于Javascript疏唾,所以React DOM規(guī)范使用駝峰(camelCase)屬性命名約定,而不是HTML屬性名稱函似,當(dāng)然槐脏,html的部分屬性名稱也作為保留字,不可使用撇寞,例如 class顿天;
因此,class 在 JSX 中 變?yōu)?className, tableindex 變?yōu)?tableIndex蔑担。

用 JSX 防止注入攻擊

? 在JSX 中嵌入用戶輸入是安全的:

const title = response.potentiallyMaliciousInput;
// 這樣是安全的
const element  = <h1>{title}</h1>

? 默認(rèn)情況下牌废, 在渲染之前, React DOM 會格式化(escapes) JSX中的所有值. 從而保證用戶無法注入任何應(yīng)用之外的代碼. 在被渲染之前,所有的數(shù)據(jù)都被轉(zhuǎn)義成為了字符串處理啤握。 以避免 XSS(跨站腳本) 攻擊鸟缕。
?

$ 元素渲染到DOM

? 正常情況下,你的 index.html 文件下會有這么一個(gè)div

<div id='root'></div>

? 這個(gè)root DOM 節(jié)點(diǎn)掛在所有React DOM的位置排抬。正常情況下叁扫,對于一個(gè)React單頁面應(yīng)用構(gòu)建三妈,只需要一個(gè)單獨(dú)的根DOM節(jié)點(diǎn)即可。但如果要把React整合到現(xiàn)有的APP中莫绣,則可能會使用到多個(gè)DOM節(jié)點(diǎn)畴蒲。

? React利用render方法將React元素渲染到DOM上,一旦元素被渲染到頁面了之后对室,就不能在修改器子元素或任何元素的屬性模燥,就像電影里的一幀,在某以特定的時(shí)間點(diǎn)的UI效果掩宜,那元素的更新呢蔫骂?沒錯,就是重新 render

function tick() {
    cosnt element = {
        <div>
            <h1>Hello, world</h1>
            <h2>It is {new Date().toLocaleTimeString()}.</h2>
        </div>
    }牺汤;
    ReactDom.render (
        element,
        document.getElementById('root')
    )
}

setInterval(tick, 1000);

實(shí)際上辽旋,大多數(shù) React 應(yīng)用只會調(diào)用一次ReactDom.render(),而實(shí)現(xiàn)組件更新的辦法就是將代碼封裝在有狀態(tài)的組件中檐迟。

React 只更新必須更新的部分

? 這正是 React 的強(qiáng)大之處补胚。React DOM 會將元素及其子元素與之前版本逐一對比,并只對有必要更新的 DOM 進(jìn)行更新, 以達(dá)到 DOM 所需的狀態(tài)追迟。

? 開發(fā)過程中溶其,更應(yīng)該每個(gè)時(shí)間點(diǎn)UI的表現(xiàn), 而不是關(guān)注隨著時(shí)間不斷更新UI的狀態(tài), 可以減少很多奇怪的 bug
?

$ 組件和屬性

? 組件 components 和屬性 props,其中敦间,屬性是單詞 property 的代碼簡寫瓶逃。

定義組件的兩種辦法

? 定義組件有兩種方式

  1. 函數(shù)式組件定義
  2. 類組件定義
    ? 最簡單的定義組件的方法就是寫一個(gè) Javascript 函數(shù)
function Welcome(props)  {
    return <h1>Hello, props.name</h1>
}

? 這就是一個(gè)有效的組件,它接首了一個(gè) props 參數(shù)廓块,并返回了一個(gè)React元素厢绝,這是一個(gè)函數(shù)式組件,表面上看带猴,他就是一個(gè) Javascript函數(shù)代芜。

? 類組件的定義則依賴ES6 的 class 來定義,下面這種定義方法和上方是等效的;

class Welcome extends React.Component {
    render() {
        return <h1>Hello, {this.props.name}</h1>;
    }
}
渲染一個(gè)組件
// DOM標(biāo)簽作為組件
const element = <div />;
// React 元素作為組件
const element = <Welcome name="Sara" />;

? 當(dāng)React 遇到一個(gè)代表用戶定義組件的元素時(shí)浓利,它將 JSX 屬性以一個(gè)單獨(dú)對象即
props對象 的形式傳遞給相應(yīng)的組件挤庇,例如

function Welcome(props) {
    return <h1>Hello, {props.mname] </h1>;
}
const element = <Wlecome name="Sara" />;
ReactDOM.render(
    element,
    document.getElementById('root')
)

理解

  1. 調(diào)用 ReactDOM.render() 方法并向其傳入了<Welcome name="Sara" />元素
  2. Raect 調(diào)用 Welcome 組件,并向其傳入了 {name: ‘Sara’} 作為 props對象
  3. Welcome 組件返回 <h1>Hello, Sara</h1>
  4. React DOM 迅速更新 DOM贷掖,使其顯示為 <h1>Hello, Sara</h1>

組件名稱總是以大寫字母開始嫡秕, 如本例子中 <Welcome />, 而不是 <welcome />

構(gòu)成組件

? 既然組件是單獨(dú)的一個(gè)React元素,那他能單獨(dú)工作苹威,因此我們能在一個(gè)React 元素中多次引用到相同的組件, 舉個(gè)例子:

function Welcome(props) {
    return <h1>Hello, {props.name}</h1>
}
function App() {
    return (
        <Welcome name="Sara" />
        <Welcome name="Lucy" />
        <Welcome name="Edite" />
    )
}

ReactDOM.render(
    <App />,
    document.getElementBuId('root')
)

? 通常情況下昆咽, React apps 都有一個(gè)單獨(dú)的頂層的 App 組件。如果是在已有的應(yīng)用中整合React,也需要由下至上的從小的組件開始逐步整合到視圖頂層的組件中掷酗。

組件必須返回一個(gè)單獨(dú)的根元素调违,這就是為什么我們要添加一個(gè) <div>來包裹所有的<Welcome /> 元素的原因

提取組件

? 對于一個(gè)React 元素,如果其中含有可復(fù)用或可能會重復(fù)使用的內(nèi)容泻轰,不要害怕把它單拿出來多個(gè)更小的組件技肩。

? 提取組件可能看起來是一個(gè)繁瑣的工作,但是在大型的 App 中可以回報(bào)給我們的是大量的可復(fù)用組件浮声。一個(gè)好的經(jīng)驗(yàn)準(zhǔn)則是如果你 UI 的一部分需要用多次 (Button虚婿,PanelAvatar)泳挥,或者本身足夠復(fù)雜(App然痊,FeedStoryComment)屉符,最好的做法是使其成為可復(fù)用組件剧浸。

Props 是只讀的

? 無論你用函數(shù)或類的方法來聲明組件,

? 雖然 React 很靈活,但是它有一條嚴(yán)格的規(guī)則:**所有 React 組件都必須是純函數(shù)矗钟,并禁止修改其自身 props **唆香。所謂的純函數(shù)就是:傳入函數(shù)參數(shù)不會在函數(shù)執(zhí)行過程中發(fā)生改變,比如自增操作 a++真仲。

? 如果props是只讀的袋马,那傳遞給子元素(子組件)的參數(shù)豈不是不能修改了初澎?那子元素如何與父元素做交互呢秸应?React還給我們提供了狀態(tài)屬性 state供我們在子組件內(nèi)部修改值
?

狀態(tài)和生命周期

? 狀態(tài)state, 生命周期 liftcircle.
? 之前說過,一旦元素被渲染了之后就不可改變了碑宴,但我們可以通過重新渲染的方法使頁面得以刷新软啼,同樣我們提到過最常用的方法是編寫一個(gè)可復(fù)用的具有狀態(tài)的組件,這里的狀態(tài)延柠,就是我們將要說的 state

? 我們對上述提過的計(jì)時(shí)器tick 中的計(jì)時(shí)功能封裝成一個(gè)函數(shù)式組件如下:

function Clock(props) {
    return (
        <div>
            <h1>Hello, world!</h1>
            <h2>It is {props.date.toLocaleTimeString()}.</h2>
        </div>
    )
}

? 然后把他當(dāng)做一個(gè)元素放入 tick 中進(jìn)行渲染

function tick() {
    ReactDOM.render(
        <Clock date={new Date()} />,
        document.getElementById('root')
    )
}

setInterval(tick, 1000);

? 在這個(gè)例子中祸挪,我們將計(jì)時(shí)功能代碼封裝成了一個(gè)獨(dú)立的可復(fù)用的組件,并通過屬性date的方式將參數(shù)傳入贞间,但還不能到達(dá)我們想要的結(jié)果贿条,那就是不能再組件內(nèi)部修改參數(shù)值,組件中顯示的數(shù)據(jù)依舊受控于父組件中date屬性傳遞過來的值增热,那如果我們把這個(gè)date屬性也添加到Clock內(nèi)部呢整以?來看下

ReactDOM.render(
    <Clock />,
    document.getElementById('root')
)

? 這時(shí)父組件中只保留了對計(jì)時(shí)組件Clock的一個(gè)單純的引用。剩下的事情全部依托以組件Clock自己去實(shí)現(xiàn)峻仇。要怎么實(shí)現(xiàn)這個(gè)需求公黑?這里React提出了另一個(gè)數(shù)據(jù)對象,即state,它用來保存組件內(nèi)部的數(shù)據(jù)凡蚜,與props類似人断,不同的是state是組件私有的,并且由組件本身完全控制朝蜘。它能實(shí)現(xiàn)數(shù)據(jù)在組件內(nèi)部的修改和更新恶迈。怎么使用這個(gè)state?繼續(xù)往下講之前我們先拓展一個(gè)知識

? 我們知道組件有兩種定義方式芹务,即函數(shù)式組件和類組件蝉绷,雖然函數(shù)式組件更加簡潔更加接近原生 javascript,但類組件卻擁有一些額外的屬性枣抱,這個(gè)類組件專有特性熔吗,就是狀態(tài)生命周期鉤子,到這里也能清楚知道狀態(tài)的關(guān)鍵作用佳晶,然而函數(shù)式組件沒有這兩個(gè)特性桅狠,因此,在需要使用到狀態(tài)state情況下轿秧,我們需要將函數(shù)式組件轉(zhuǎn)換成類組件

函數(shù)式組件轉(zhuǎn)化成類組件

? 嘗試把一個(gè)函數(shù)式組件轉(zhuǎn)化成類組件中跌,官網(wǎng)給出了以下步驟,以Clock組件為例

  1. 創(chuàng)建一個(gè)繼承自 React.Component 類的 ES6 class 同名類
  2. 添加一個(gè)名為 render() 的空方法
  3. 把原函數(shù)中的所有內(nèi)容移至 render()
  4. render() 方法中使用 this.props 替代 props
  5. 刪除保留的空函數(shù)聲明
class Clock extents React.Component {
    render() {
        return (
            <div>
                <h1>Hello, world</h1>
                <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
            </div>
        )
    }
}

? 到此菇篡,Clock 組件已經(jīng)成功被我們修改成了一個(gè)類組件漩符,我們便可以在其中添加本地狀態(tài)state和生命周期鉤子

class Clock extends React.Component {
    // 用類構(gòu)造函數(shù)constructor初始化 this.state
    constructor(props) {
        // 使用super()將props傳遞給基礎(chǔ)構(gòu)造函數(shù)
        super(props);
        this.state = {date: new Date()};
    }

    render() {
        return (
            <div>
                <h1>Hello, world</h1>
                <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
            </div>
        )
    }
}

? 這樣,我們的類組件Clock 就擁有了自己的屬性 this.state.date驱还,也就不需要引用組件向其傳遞值了嗜暴,因此,我么可以把組件引用中的date屬性刪掉议蟆,最終闷沥,我們將其渲染到DOM上,只使用組件引用咐容,其他都交給組件Clock自己去實(shí)現(xiàn)

ReactDOM.render(
    <Clock />,
    document.getElementById('root')
)

? 到這里就結(jié)束了舆逃?細(xì)心的你會發(fā)現(xiàn),組件Clock只是實(shí)現(xiàn)了當(dāng)前時(shí)間的顯示戳粒,而我們要改裝的功能是一個(gè)計(jì)時(shí)器路狮,計(jì)時(shí)功能去哪里了?沒實(shí)現(xiàn)拔翟肌奄妨?我們需要在組件Clock中找到一個(gè)合適的時(shí)機(jī)去實(shí)現(xiàn)這個(gè)功能,為此炊琉,React團(tuán)隊(duì)引入了 聲明周期方法展蒂,也叫生命周期鉤子

在類組件中添加生命周期方法

? 在一個(gè)具有許多組件的應(yīng)用程序中又活,在組件被銷毀時(shí)釋放所占用的資源是非常重要的。就像瀏覽器的垃圾回收機(jī)制锰悼,近期內(nèi)不需要再用的資源柳骄,應(yīng)該及時(shí)清除。

? 當(dāng) Clock 第一次渲染到DOM時(shí)箕般,我們要設(shè)置一個(gè)定時(shí)器 耐薯。 這在 React 中稱為 “掛載(mounting)” 。它有一個(gè)生命鉤子componentDidMount()

當(dāng) Clock 產(chǎn)生的 DOM 被銷毀時(shí)丝里,我們也想清除該計(jì)時(shí)器曲初。 這在 React 中稱為 “卸載(unmounting)” 。它的生命鉤子是componentWillUnmount()

? 我們的計(jì)時(shí)器是在頁面加載之后杯聚,頁面生成初始化狀態(tài)臼婆,然后由計(jì)時(shí)器去觸發(fā)狀態(tài)的刷新,因此幌绍,在掛載完成是去設(shè)置計(jì)時(shí)器是個(gè)非常不錯的選擇

componentDidMount() {
    this.timerID = setInterval(
        () => this.tick(), 1000
    )
}

? 這樣我們就實(shí)現(xiàn)了組件計(jì)時(shí)功能颁褂,或許你注意到了,在該例中傀广,我們把timerID存放在this中而不是this.state中颁独。

? 其實(shí),this.propsthis.state也是數(shù)據(jù)對象與普通對象一樣用來存放數(shù)據(jù)伪冰,只是他們被React團(tuán)隊(duì)賦予了新的職能誓酒, this.props由React本身設(shè)定,用來存放在組件引用時(shí)的屬性鍵值對對象集贮聂,不允許Coder們自己去修改靠柑;而this.state也具有特殊的含義,即存放組件本身的寂汇、用于視覺輸出的數(shù)據(jù)病往,但也不是說在編寫React程序的時(shí)候就必須用用這兩個(gè)捣染,我們依然可以自己定義普通的數(shù)據(jù)結(jié)構(gòu)骄瓣。

? 既然state是用于存放組件視覺輸出的數(shù)據(jù),那在render()方法中沒有被引用的耍攘,就不應(yīng)該出現(xiàn)在state中了榕栏。

? 養(yǎng)成良好的編碼習(xí)慣,編寫好計(jì)時(shí)器時(shí)蕾各,及時(shí)的編寫卸載事件扒磁。卸載時(shí)我們清除的數(shù)據(jù)也是從this中拿的。

componentWillUnmount() {
    clearInterval(this.timerID);
}

? 掛載時(shí)我們聲明了一個(gè)tick()方法式曲,接下來我們就要實(shí)現(xiàn)這個(gè)方法妨托,是用來觸發(fā)UI更新缸榛。嗯哼?UI更新兰伤?我們的頁面狀態(tài)state不是已經(jīng)更新了嗎内颗?為啥還要UI更新?

? 這里有一個(gè)非常重要的方法:setState()敦腔。我們先把代碼補(bǔ)充完整再說明

componentDidMount() {
    // ...
}

tick() {
    this.setState({
        date: new Date()
    })
}

componentWillUnmount() {
    // ...
}

? setState()是React觸發(fā)頁面更新的第二個(gè)辦法均澳,第一個(gè)辦法開篇即說過,即render()方法符衔。setState作用就是通知React檢查帶狀態(tài)的組件中是否含有臟值找前。此時(shí)react會生成一個(gè)虛擬DOM與之前的版本進(jìn)行對比,只有有必要更新時(shí)才會更新判族。關(guān)于 state 與 setState過程 在我的另一篇文章中有詳細(xì)說明躺盛,有興趣的可以翻過去看看。

? 為什么不把tick()方法寫到componentDidMount()中形帮?因?yàn)?code>tick()只是一個(gè)普通方法颗品,他不需要在生命周期中觸發(fā),也不用自動觸發(fā)沃缘。只要誰調(diào)用了觸發(fā)即可躯枢。因此不需要也不能放在生命周期鉤子函數(shù)中。

? 現(xiàn)在這個(gè)時(shí)鐘每秒都會走了槐臀。整理一下脱吱,我們整個(gè)計(jì)時(shí)器代碼如下:

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

整個(gè)流程的執(zhí)行過程是這樣的:

  1. 當(dāng) <Clock /> 被傳入ReactDOM.render() 時(shí), React 會調(diào)用 Clock組件的構(gòu)造函數(shù)迂求。 因?yàn)?Clock 要顯示的是當(dāng)前時(shí)間,所以它將使用包含當(dāng)前時(shí)間的對象來初始化 this.state。我們稍后會更新此狀態(tài)菩帝。

  2. 然后 React 調(diào)用了 Clock 組件的 render() 方法。 React 從該方法返回內(nèi)容中得到要顯示在屏幕上的內(nèi)容油航。然后糊闽,React 然后更新 DOM 以匹配 Clock 的渲染輸出。

  3. 當(dāng) Clock 輸出被插入到 DOM 中時(shí)谍珊,React 調(diào)用 componentDidMount() 生命周期鉤子治宣。在該方法中,Clock 組件請求瀏覽器設(shè)置一個(gè)定時(shí)器來一次調(diào)用 tick()砌滞。

  4. 瀏覽器會每隔一秒調(diào)用一次 tick()方法侮邀。在該方法中, Clock 組件通過 setState() 方法并傳遞一個(gè)包含當(dāng)前時(shí)間的對象來安排一個(gè) UI 的更新贝润。通過 setState(), React 得知了組件 state(狀態(tài))的變化, 隨即再次調(diào)用 render() 方法绊茧,獲取了當(dāng)前應(yīng)該顯示的內(nèi)容。 這次打掘,render() 方法中的 this.state.date 的值已經(jīng)發(fā)生了改變华畏, 從而鹏秋,其輸出的內(nèi)容也隨之改變。React 于是據(jù)此對 DOM 進(jìn)行更新亡笑。

  5. 如果通過其他操作將 Clock 組件從 DOM 中移除了, React 會調(diào)用 componentWillUnmount() 生命周期鉤子, 所以計(jì)時(shí)器也會被停止拼岳。
    ?

正確的使用State(狀態(tài))

? 對于setState() 有三件事情是我們應(yīng)該要知道的

(1)不要直接修改state
? 真正觸發(fā)React對比不同版本的虛擬DOM是setState() 方法,直接修改state頁面不會刷新况芒,這一點(diǎn)與原生javascript區(qū)別較大惜纸,需要理解。

// 這么做不會觸發(fā)React更新頁面
this.state.comment = 'hello';
// 使用 setState() 代替
this.setState({ comment: 'hello' });

? 【注意】在組件中绝骚,唯一可以初始化分配this.state的地方就是構(gòu)造函數(shù)constructor(){}

(2)state(狀態(tài))更新可能是異步的
? React為了優(yōu)化性能耐版,有可能會將多個(gè)setState() 調(diào)用合并為一次更新。這就導(dǎo)致 this.propsthis.state 可能是異步更新的压汪,你不能依賴他們的值計(jì)算下一個(gè)state(狀態(tài))

// counter 計(jì)數(shù)更新會失敗
this.setState({
    counter: this.state.counter this.props.increment
})

? 如果我們有這種需求粪牲,可以使用以下setState()辦法:

// ES6 箭頭函數(shù)法
this.setState((prevState, props) => ({
    counter: prevState.counter + props.increment
}));
// 常規(guī)函數(shù)法
this.setState(function(prevState, props) {
    return {
        counter: prevState.counter + props.increment
    };
})

(3)state(狀態(tài))更新會被合并
當(dāng)你調(diào)用setState(), React將合并你提供的對象到當(dāng)前狀態(tài)中。例如止剖,你的狀態(tài)可能包含幾個(gè)獨(dú)立的變量腺阳,然后你用幾個(gè)獨(dú)立的setState方法去調(diào)用更新,如下

constructor(props) {
    super(props);
    this.state = {
        posts: [],
        comments: []
    };
}

componentDidMount() {
    fetchPosts().then(response => {
        this.setState({
            posts: response.posts
        });
    });

    fetchComments().then(response => {
        this.setState({
            comments: response.comments
        });
    });
  }

? 合并是淺合并穿香,所以亭引,this.setState({comments})在合并過程中不會改變this.state.posts的值,但是會完全替換this.state.comments 的值

數(shù)據(jù)向下流動

? 無論是作為父組件還是子組件皮获,它都無法或者一個(gè)組件是否有狀體焙蚓,同時(shí)也不需要關(guān)心另一個(gè)組件是定義為函數(shù)組件還是類組件。這就是為什么state經(jīng)常被稱為 本地狀態(tài)封裝狀態(tài) 的原因洒宝, 他不能被擁有并設(shè)置它的組件以外的任何組件訪問购公。那如果需要訪問怎么處理?
(1)作為其子組件的props(屬性)

// 在組件中使用
<h2>It is {this.state.date.toLocaleTimeString()}</h2>
// 傳遞給子組件作為props
<FormattedDate date={this.state.date} />

? 雖然FormattedDate組件通過props接收了date的值雁歌,但它仍然不能獲知該值是來自于Clockstate, 還是 Clockprops, 或者一個(gè)手動創(chuàng)建的變量.

? 這種數(shù)據(jù)關(guān)系宏浩,一般稱為"從上到下"或"單向"的數(shù)據(jù)流。任何state(狀態(tài))始終由某個(gè)特定組件所有靠瞎,并且從該state導(dǎo)出的任何數(shù)據(jù) 或 UI 只能影響樹"下方"的組件

? 如果把組件樹想像為 props(屬性) 的瀑布比庄,所有組件的 state(狀態(tài)) 就如同一個(gè)額外的水源匯入主流,且只能隨著主流的方向向下流動较坛。

各組件完全獨(dú)立

? 借用上文的Clock組件印蔗,我們創(chuàng)建一個(gè)App組件扒最,并在其中渲染三個(gè)Clock:

function App() {
    return (
        // 之前說過組件只能返回一個(gè)根節(jié)點(diǎn)丑勤,所以用<div>包起來
        <div>
            <Clock />
            <Clock />
            <Clock />
        </div>
    );
}
ReactDOM.render(
    <App />,
    document.getElementById('root')
);

? 每個(gè)Clock都設(shè)立它自己的計(jì)時(shí)器并獨(dú)立更新,如果App中有一個(gè)數(shù)據(jù)變量吧趣,也能被三個(gè)Clock相互獨(dú)立修改法竞。

? 至于何時(shí)使用有狀態(tài)組件耙厚,何時(shí)使用無狀態(tài)組件,被認(rèn)為是組件的一個(gè)實(shí)現(xiàn)細(xì)節(jié)岔霸,取決于你當(dāng)時(shí)的需求薛躬,你可以在有狀態(tài)組件中使用無狀態(tài)組件,也可以在無狀態(tài)組件中使用有狀態(tài)組件
?

$ 事件處理

? 通過 React 元素處理事件跟在 DOM 元素上處理事件非常相似呆细。但是有一些語法上的區(qū)別:

  1. React 事件使用駝峰命名型宝,而不是全部小寫
  2. 通過 JSX , 傳遞一個(gè)函數(shù)作為事件處理程序,而不是一個(gè)字符串
// html usage
<button onclick="todo()">click me</button>
// React usage
<button onClick={todo}>click me></button>
  1. 在React中不能通過返回false來阻止默認(rèn)行為絮爷。必須明確的調(diào)用preventDefault
// html usage
<a href="#" onclick="console.log('clicked'); return false">
    Click me
</a>
// React usage
class ActionLink extends React.Component {
    function handleClick(e) {
        e.preventDefault();
        console.log('clicked.');
    }
    return (
        <a href="#" onClick={handleClick}>
            Click me
        </a>
    )
}

? 在這里趴酣,React團(tuán)隊(duì)幫Coder們實(shí)現(xiàn)了e事件的跨瀏覽器兼容問題。當(dāng)使用React時(shí)坑夯,我們也不需要調(diào)用addEventListener在DOM 元素被創(chuàng)建后添加事件監(jiān)聽器岖寞。相反,只要當(dāng)元素被初始渲染的時(shí)候提供一個(gè)監(jiān)聽器就可以了柜蜈。

? 當(dāng)使用ES6類定義一個(gè)組件時(shí)仗谆,通常的一個(gè)事件處理程序就是類上的一個(gè)方法,看個(gè)例子淑履,Toggle 組件渲染一個(gè)按鈕隶垮,讓用戶在 “ON” 和 “OFF” 狀態(tài)之間切換:

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // 這個(gè)綁定是必要的,使`this`在回調(diào)中起作用
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);
綁定類方法

? 在JSX回調(diào)中你必須注意 this 的指向秘噪。 在 JavaScript 中岁疼,類方法默認(rèn)沒有 綁定 的。如果你忘記綁定 this.handleClick 并將其傳遞給onClick缆娃,那么在直接調(diào)用該函數(shù)時(shí)捷绒,this 會是 undefined

?這不是 React 特有的行為贯要;這是 JavaScript 中的函數(shù)如何工作的一部分暖侨,可以使用屬性初始值設(shè)置來正確地 綁定(bind) 回調(diào),但這是實(shí)驗(yàn)性做法崇渗,不建議使用字逗,以后有可能會廢棄,如果你沒有使用屬性初始化語法
(1)可以在回調(diào)中使用一個(gè) arrow functions

class LoginButton extends React.Component {
    handleClick() {
        console.log('this is: ', this)
    }

    render() {
        // 這個(gè)語法確保 `this` 被綁定在 handleClick 中
        return (
            <button onClick={(e) => this.handleClick(e)}>
                Click me
            </button>
        );
    }
}

(2)使用Function.prototype.bind 方法宅广,相對簡潔方便

<button onClick={this.handleClick.bind(this)}>
    Click me
</button>
傳遞參數(shù)給事件處理程序

? 在循環(huán)內(nèi)部葫掉,通常需要將一個(gè)額外的參數(shù)傳遞給事件處理程序,常用的有一下兩種方案;

<button onClick={(e)  => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this.id)}>Delete Row</button>

? 上面兩個(gè)例子中跟狱,參數(shù) e 作為 React 事件對象將會被作為第二個(gè)參數(shù)進(jìn)行傳遞俭厚。通過箭頭函數(shù)的方式,事件對象必須顯式的進(jìn)行傳遞驶臊,但是通過 bind 的方式挪挤,事件對象以及更多的參數(shù)將會被隱式的進(jìn)行傳遞叼丑。
?

$ 條件渲染

在 React 中,你可以創(chuàng)建不同的組件封裝你所需要的行為扛门。然后鸠信,只渲染它們之中的一些,取決于你的應(yīng)用的狀態(tài)论寨。

整個(gè)組件的條件渲染

? React 中的條件渲染就可在JS中的條件語句一樣星立,使用JS操作符如if或者條件控制符來創(chuàng)建渲染當(dāng)前的元素,并且讓React更新匹配的UI葬凳。比如我們有一個(gè)需求贞铣,需要判斷用戶是否登錄,來顯示不同組件

function UserGreeting(props) {
    return <h1>Welcome back!</h1>
}

function GustGrreeting(props) {
    return <h1>Please sign up.</h1>
}
function Greeting(props) {
    const isLoggedIn = props.isLoggedIn;
    if (isLoggedIn) {
        return <UserGreeting />
    } 
    return <GuestGreeting />
}

ReactDOM.render(
    <Greeting isLoggedIn={false} />,
    document.getElementById('root')
);
使用元素變量條件渲染部分內(nèi)容

? 你可以用變量來存儲元素沮明。這可以幫助您有條件地渲染組件的一部分辕坝,而輸出的其余部分不會更改。下方兩個(gè)組件用于顯示登出和登入按鈕

function LoginButton() {
    return(
        <button onClick={props.onClick}>Login</button>
    )
}

function LogoutButton(props) {
    return (
        <button onClick={props.onclick}>Logout</button>
    )
}

? 登入登出按鈕已做好荐健,接下來需要實(shí)現(xiàn)有切換功能的一個(gè)有狀態(tài)的組件酱畅,為了更系統(tǒng)化學(xué)習(xí),我們把前面的Greeting組件一起加進(jìn)來

class LoginControl extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            isLoginedIn: false
        }
    }

    handleLoginClick() {
        this.setState({   isLoggedIn: true });
    }

    handleLogoutClick() {
        this.setState({ isLoggedIn: false });
    }

    render() {
        const isLoggedIn = this.state.isLoggedIn;

        let button = null;
        if (isLoggedIn) {
            button = <LogoutButton onClick={this.handleLogoutClick.bind(this)} />
        } else {
            button = <LoginButton onclick={this.handleLoginClick.bind(this)} />
        }

        return (
            <div>
                <Greeting isLoggedIn={isLoggedIn} />{button}</div>
            </div>
        )
    }
}

reactDOM.render(
    <LoginControl />,
    document.getElementById('root')
)

? 使用if是很常見的一種做法江场,當(dāng)然也有一些更簡短的語纺酸。JSX中有幾種內(nèi)聯(lián)條件的方法,

(1)使用邏輯與&&操作符的內(nèi)聯(lián)if用法
? 我們可以在 JSX 中嵌入任何表達(dá)式址否,方法是將其包裹在花括號中餐蔬,同樣適用于JS的邏輯與&&運(yùn)算符

function Mailbox(props) {
    const unreadMessages = props.unreadMessages;
    return (
        <div>
            <h1>Hello!</h1>
            { unreadMeaasges.length > 0 &&
                <h2> You have {unreadMessages.length} unread messages.
            }
        </div>
    )
}

cosnt message = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
    <Mailbox unreadMessages={messages} />,
    document.getElementById('root')
);

? 該案例是可以正常運(yùn)行的,因?yàn)樵?JavaScript 中佑附, true && expression 總是會評估為 expression 樊诺,而 false && expression 總是執(zhí)行為 false 。并且我們可以在表達(dá)式中嵌入表達(dá)式

(2)使用條件操作符的內(nèi)聯(lián)If-Else
? 條件操作符 即三目表達(dá)式:condition 音同? trueExpression : falseExpression

// 條件渲染字符串
<div>The user is {isLoggedIn ? 'currently' : 'not'} logged in.</div>
// 條件渲染組件
<div>
    {isLoggedIn ? (
        <LogoutButton onClick={this.handleLogoutClick} />
    ) : (
        <LoginButton onClick={this.handleLoginClick} />
    )}
</div>

? 總之词爬,遵循一個(gè)原則,哪種方式易于閱讀权均,就選擇哪種寫法顿膨。并且,但條件變得越來越復(fù)雜時(shí)叽赊,可能是提取組件的好時(shí)機(jī)恋沃。

阻止組件渲染

? 在極少數(shù)情況下,您可能希望組件隱藏自身必指,即使它是由另一個(gè)組件渲染的囊咏。為此,返回 null 而不是其渲染輸出。注意這里是不渲染匆笤,不是不顯示研侣。

? 在下面的例子中谱邪,根據(jù)名為warnprops 值炮捧,呈現(xiàn) <WarningBanner /> 。如果 props 值為 false 惦银,則該組件不渲染:

function WarningBanner(props) {
    if (props.warn) { 
        return null;
    }
    
    return (
        <div className="warning">Warning</div>
    )
}

class Page extends React.Component {
    constructor(props) {
        super(props);
        this.state = { showWarning: true }
    }

    handleToggleClick() {
        this.setState(prevState => ({
            showWarning: !prevState.showWarning
        }));
    }

    render() {
        return (
            <div>
                <Warningbanner warn={this.state.showWarning} />
                <button onClick={this.handleToggleClick.bind(this)}>
                    { this.state.showWarning ?   'Hide' : 'Show'}
                 </button>
            </div>
        )
    }
}

ReactDOM.render(
    <Page />,
    document.getElementById('root')
)

? 從組件的 render 方法返回 null 不會影響組件生命周期方法的觸發(fā)咆课。 例如, componentWillUpdatecomponentDidUpdate 仍將被調(diào)用扯俱。因此需要組件剛載入時(shí)就要判斷執(zhí)行返回null

$ 后語

? 本文為React系統(tǒng)性需學(xué)習(xí)上半文书蚪,下半文主要包括:

  1. 列表(List) 和 鍵(keys)
  2. 表單(Forms)
  3. 狀態(tài)提升(Lifting State Up)
  4. 組合 VS 繼承 (Composition vs inheritance)
    ? 如果有錯誤,歡迎大家指正迅栅,也歡迎大家到評論區(qū)交流共同進(jìn)步
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末殊校,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子读存,更是在濱河造成了極大的恐慌为流,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件让簿,死亡現(xiàn)場離奇詭異敬察,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)尔当,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門莲祸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人椭迎,你說我怎么就攤上這事锐帜。” “怎么了畜号?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵抹估,是天一觀的道長。 經(jīng)常有香客問我弄兜,道長药蜻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任替饿,我火速辦了婚禮语泽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘视卢。我一直安慰自己踱卵,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著惋砂,像睡著了一般妒挎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酝掩,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天眷柔,我揣著相機(jī)與錄音,去河邊找鬼驯嘱。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鞠评,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播聋涨,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼锥忿!你這毒婦竟也來了牛郑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤敬鬓,失蹤者是張志新(化名)和其女友劉穎淹朋,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钉答,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡础芍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了数尿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仑性。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖右蹦,靈堂內(nèi)的尸體忽然破棺而出诊杆,到底是詐尸還是另有隱情,我是刑警寧澤何陆,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布晨汹,位于F島的核電站,受9級特大地震影響贷盲,放射性物質(zhì)發(fā)生泄漏淘这。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望铝穷。 院中可真熱鬧钠怯,春花似錦、人聲如沸曙聂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽筹陵。三九已至刽锤,卻和暖如春镊尺,著一層夾襖步出監(jiān)牢的瞬間庐氮,已是汗流浹背弄砍。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工音婶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留衣式,地道東北人碴卧。 一個(gè)月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓住册,卻偏偏與公主長得像荧飞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子挠轴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容

  • HTML模版 之后出現(xiàn)的React代碼嵌套入模版中。 1. Hello world 這段代碼將一個(gè)一級標(biāo)題插入到指...
    ryanho84閱讀 6,243評論 0 9
  • 以下內(nèi)容是我在學(xué)習(xí)和研究React時(shí)委煤,對React的特性碧绞、重點(diǎn)和注意事項(xiàng)的提取讥邻、精練和總結(jié),可以做為React特性...
    科研者閱讀 8,236評論 2 21
  • 本筆記基于React官方文檔,當(dāng)前React版本號為15.4.0发魄。 1. 安裝 1.1 嘗試 開始之前可以先去co...
    Awey閱讀 7,712評論 14 128
  • 原教程內(nèi)容詳見精益 React 學(xué)習(xí)指南汰寓,這只是我在學(xué)習(xí)過程中的一些閱讀筆記苹粟,個(gè)人覺得該教程講解深入淺出嵌削,比目前大...
    leonaxiong閱讀 2,840評論 1 18
  • 偶爾跟之前教過的學(xué)生聊天掷贾,問詢她的近況想帅,她說了一些工作情況,也沒忘記跟她的老師我訴訴苦:“工作兩三年了旨剥,攢錢借錢在...
    楊子老師閱讀 610評論 1 2