React 學(xué)習(xí)筆記1——基礎(chǔ)概念篇

1.1React 簡(jiǎn)介

React 把用戶界面抽象成一個(gè)個(gè)組件,開(kāi)發(fā)者通過(guò)組合這些組件,最終得到功能豐富、可交互的頁(yè)面。通過(guò)引入 JSX 語(yǔ)法,復(fù)用組件變得非常容易,同時(shí)也能保證組件結(jié)構(gòu)清晰。有了組件這層抽象,React 把代碼和真實(shí)渲染目標(biāo)隔離開(kāi)來(lái),除了可以在瀏覽器端渲染 DOM 來(lái)開(kāi)發(fā)網(wǎng)頁(yè),還能用于開(kāi)發(fā)原生移動(dòng)應(yīng)用。

1.1.1 專注視圖層

React 并不是完整的 MVC/MVVM 框架,它專注于提供清晰、簡(jiǎn)潔的 View(視圖)層解決方案,又是一個(gè)包括 View 和 Controller 的庫(kù)。對(duì)于復(fù)雜的應(yīng)用,可根據(jù)應(yīng)用場(chǎng)景自行選擇業(yè)務(wù)層框架,并根據(jù)需要搭配 Flux、Redux、Graph/Relay 來(lái)使用。

1.1.2 Virtual DOM

真實(shí)頁(yè)面對(duì)應(yīng)一個(gè) DOM 樹(shù)。傳統(tǒng)頁(yè)面開(kāi)發(fā)中,每次要更新頁(yè)面時(shí),都要手動(dòng)操作 DOM 來(lái)更新,如圖:

傳統(tǒng)頁(yè)面開(kāi)發(fā)更新頁(yè)面

但在前端開(kāi)發(fā)中,性能消耗最大的就是 DOM 操作,且這部分代碼會(huì)讓整體項(xiàng)目的代碼變得難以維護(hù)。

React 把真實(shí) DOM 樹(shù)轉(zhuǎn)換成 JavaScript 對(duì)象樹(shù),即 Virtual DOM,如圖:

React Virtual DOM 頁(yè)面更新

每次數(shù)據(jù)更新后,重新計(jì)算 Virtual DOM,并和上一次生成的 Virtual DOM 做對(duì)比,對(duì)發(fā)生變化的部分做批量更新。

React 也提供了直觀的 shouldComponentUpdate 生命周期函數(shù),來(lái)減少數(shù)據(jù)變化后不必要的 Virtual DOM 對(duì)比過(guò)程,以保證性能。

Virtual DOM 最大的好處在于方便和其他平臺(tái)集成:如 react-native 就是基于 Virtual DOM 渲染出的原生控件,React 組件可以映射為對(duì)應(yīng)的原生控件,輸出時(shí),是輸出 Web DOM、Android 控件還是 iOS 控件,則由平臺(tái)本身決定。

1.1.3 函數(shù)式編程

函數(shù)式編程,對(duì)應(yīng)的是聲明式編程,它是人類模仿自己邏輯思考方式發(fā)明出來(lái)的。
React 把過(guò)去不斷重復(fù)構(gòu)建 UI 的過(guò)程抽象成了組件,且在給定參數(shù)的情況下約定渲染對(duì)應(yīng)的 UI 界面。
React 能充分利用很多函數(shù)式方法去減少冗余代碼。
且由于它本身就是簡(jiǎn)單函數(shù),所以易于測(cè)試。

可以說(shuō),函數(shù)式編程才是 React 的精髓。

1.2 JSX 語(yǔ)法

React 為方便 View 層組件化,承載了構(gòu)建 HTML 結(jié)構(gòu)化頁(yè)面的職責(zé),但 React 是通過(guò)創(chuàng)建與更新虛擬元素來(lái)管理整個(gè) Virtual DOM 的。

虛擬元素可以理解為真實(shí)元素的對(duì)應(yīng),它的構(gòu)建與更新都是在內(nèi)存中完成的,并不會(huì)真正渲染到 DOM 中去。在 React 中創(chuàng)建的虛擬元素可分為兩類: DOM 元素和組件元素,分別對(duì)應(yīng)原生 DOM 元素和自定義元素。

1.2.1 JSX 的由來(lái)

  • 1. DOM 元素
    用 HTML 語(yǔ)法表示一個(gè)按鈕:
<button class="btn btn-blue">
    <em>Confirm</em>
</button>

Web 頁(yè)面是由一個(gè)個(gè) HTML 元素嵌套組合而成,當(dāng)時(shí)用 JavaScript 來(lái)描述這些元素時(shí),這些元素可以簡(jiǎn)單地被表示成純粹的對(duì)象,如上面的 button,用JavaScript 描述則如下,且依舊包括元素類型及屬性:

    {
            type: 'button',
            props: {
                className: 'btn btn-blue',
                children: [{
                    type: 'em',
                    props: {children: 'Confirm'}
                }]
          }
    }

這樣,我們就可以在 JavaScript 中創(chuàng)建 Virtual DOM 元素了。
但在 React 中,我們無(wú)法通過(guò)方法去調(diào)用這些元素,它們只是不可變的描述對(duì)象。

  • 2. 組件元素
    如下,我們可以封裝上述 button 元素,得到一種構(gòu)建按鈕的公共方法:
        const Button = ({ color, text }) => {
            return {
                type: 'button',
                props: {
                    className: `btn btn-${color}`,
                    children: {
                        type: 'em',
                        props: {    children: text,  },
                    },
                },
            };
        }

我們要生成 DOM 元素中具體的按鈕時(shí),就可以方便的調(diào)用
Button({color: 'blue', text: 'confirm'})
來(lái)創(chuàng)建。

那么上面你的 Button 方法也可以作為元素而存在,方法名對(duì)應(yīng)元素類型,參數(shù)對(duì)應(yīng) DOM 元素屬性,它就具備了元素的兩大必要條件,這樣構(gòu)建的就是自定義類型的元素(組件元素)。

用 JSON 結(jié)構(gòu)來(lái)描述則如下:
{ type: Button, props: { color: 'blue', children: 'Confirm' } }

接下來(lái)我們可以再對(duì) Button 進(jìn)行封裝:

        const DangerButton = ({ text }) => ({
            type: Button,
            props: {
                  color: 'red',
                children: text
              }
        });

也可以繼續(xù)封裝更復(fù)雜些的功能模塊:

        const DeleteAccount = () => ({type: 'div',
            props: {
                children: [{type: 'p', props: {children: 'Are you sure?',},}, {
                    type: DangerButton,
                    props: {children: 'Confirm',},
                }, {type: Button, props: {color: 'blue', children: 'Cancel',},}],
            }
        });

但在表達(dá)這種結(jié)構(gòu)時(shí),就有些繁瑣,就有了 JSX ,用 JSX 語(yǔ)法來(lái)描述上述組件元素如下:

        const DeleteAccount = () => (
            <div>
                <p>Are you sure?</p>
                <DangerButton>Confirm</DangerButton>
                <Button color="blue">Cancel</Button>
            </div>
        );

這樣就簡(jiǎn)潔了許多。

小結(jié):
JSX 是 JavaScript 語(yǔ)言的一種語(yǔ)法擴(kuò)展,JSX 結(jié)構(gòu)其實(shí)就是 JavaScript 對(duì)象。編譯時(shí),這種 JSX 結(jié)構(gòu)會(huì)被轉(zhuǎn)換成 JavaScript 的對(duì)象結(jié)構(gòu)。所以,使用 React 和 JSX 一定要經(jīng)過(guò)編譯。

1.2.2 JSX 基本語(yǔ)法

可以說(shuō),JSX 基本語(yǔ)法基本被 XML 囊括了,但也有少許不同之處。下面從基本語(yǔ)法、元素類型、元素屬性、JavaScript 屬性表達(dá)式等維度一一講述。

1.2.2.1 XML 基本語(yǔ)法

使用類 XML 語(yǔ)法的好處是標(biāo)簽可以任意嵌套,我們可以像 HTML 一樣清晰地看到 DOM 樹(shù)狀結(jié)構(gòu)及其屬性。如構(gòu)造一個(gè) LIst 組件:

        const List = () => (
            <div>
                <Title>This is title</Title>
                <ul>
                    <li>list item</li>
                    <li>list item</li>
                    <li>list item</li>
                </ul>
            </div>
        );

注:

  • 定義標(biāo)簽時(shí),只允許被一個(gè)標(biāo)簽包裹:即最外層只能是一個(gè)標(biāo)簽,不能使并列的標(biāo)簽,否則會(huì)報(bào)錯(cuò)。
  • 標(biāo)簽一定要閉合: <div></div>、<p></p>之類標(biāo)簽一定要閉合, HTML 中自閉合的標(biāo)簽則可以自自合。自定義標(biāo)簽可根據(jù)是否有子組件或文本來(lái)決定是否閉合。
1.2.2.2. 元素類型

如 1.2 節(jié)中說(shuō)到兩種元素:DOM 元素組件元素。在 JSX 里對(duì)應(yīng)規(guī)則是:DOM 元素 HTML 標(biāo)簽首字母小寫(xiě),組件元素 HTML 標(biāo)簽首字母大寫(xiě)。

HTML 標(biāo)準(zhǔn)中,還有些特殊的標(biāo)簽

注釋:
HTML 中,注釋寫(xiě)成 這樣的形式,但 JSX 中并沒(méi)有定義注釋的轉(zhuǎn)換方法,事實(shí)上,JSX 還是 JavaScript,依然可以用簡(jiǎn)單方法注釋,但在一個(gè)組建的子元素位置使用注釋要用 {} 包起來(lái),否則會(huì)在頁(yè)面中直接顯示出來(lái)。如:

        const App = (
            <Nav>
                {/* 節(jié)點(diǎn)注釋 */}
                <Person
                    /* 多行 
                      注釋 */
                    name={window.isLoggedIn ? window.name : ''}/>
            </Nav>
        );

DOCTYPE:
DOCTYPE 頭是一個(gè)非常特殊的標(biāo)志,一般會(huì)在使用 React 作為服務(wù)端渲染時(shí)用到。在 HTML 中,DOCTYPE 是沒(méi)有閉合的,也就是說(shuō)我們無(wú)法渲染它。 常見(jiàn)的做法是構(gòu)造一個(gè)保存 HTML 的變量,將 DOCTYPE 與整個(gè) HTML 標(biāo)簽渲染后的結(jié)果串連起來(lái)。第 7 章會(huì)詳細(xì)講到。

1.2.2.3. 元素屬性

DOM 元素:
DOM 元素的屬性是標(biāo)準(zhǔn)規(guī)范屬性,但有兩個(gè)例外——class 和 for,在 JavaScript 中這兩個(gè)單詞都是關(guān)鍵詞。因此,React 將其這樣轉(zhuǎn)換:

class 屬性改為 className;
for 屬性改為 htmlFor。

組件元素:
組件元素的屬性是完全自定義的屬性,也可以理解為實(shí)現(xiàn)組件所需要的參數(shù)。如:

const Header = ({title, children}) => ( <h3 title={title}>{children}</h3> );

我們給 Header 組件加了一個(gè) title 屬性,則可以這樣調(diào)用:

<Header title="hello world">Hello world</Header>

可以看出,Header 中 title 屬性代表的是自定義標(biāo)簽的屬性可以傳遞;
h3 中的 title 屬性是標(biāo)簽自帶的屬性無(wú)法傳遞。

注:自定義屬性都使用小駝峰寫(xiě)法。

JSX 特有的屬性表達(dá):

Boolean 屬性:Boolean 屬性省略時(shí)默認(rèn)為 true,要傳 false,則必須使用屬性表達(dá)式。如下:

<Checkbox checked={true} /> 可以簡(jiǎn)寫(xiě)為 <Checkbox checked />,反之,設(shè)置 checked 屬性為 false 時(shí)則必須寫(xiě): <Checkbox checked={false} />。

展開(kāi)屬性:
如果事先知道組件需要的全部屬性,JSX 可以這樣來(lái)寫(xiě):
var component = <Component foo={x} bar={y} />;
如果你不知道要設(shè)置哪些 props,那么現(xiàn)在好不要設(shè)置它:

var component = <Component />; 
component.props.foo = x; // 不好 
component.props.bar = y; // 同樣不好

上面這是反模式,因?yàn)?React 不能幫你檢查屬性類型(propTypes)。這樣即使你的 屬性類型有錯(cuò)誤也不能得到清晰的錯(cuò)誤提示。

Props 應(yīng)該被認(rèn)為是不可變的。在別處修改 props 對(duì)象可能會(huì)導(dǎo)致預(yù)料之外的結(jié)果,所以原則上這將是一個(gè)凍結(jié)的對(duì)象。

那么你可以使用 JSX 的新特性——展開(kāi)屬性:(使用了 ES6 的 rest/spread 特性(...))

var props = {};
props.foo = x; 
props.bar = y;
var component = <Component {...props} />;

傳入對(duì)象的屬性會(huì)被復(fù)制到組件內(nèi)。它能被多次使用,也可以和其它屬性一起用。注意順序很重要,后面的會(huì)覆蓋掉前面的。

var props = { foo: 'default' }; 
var component = <Component {...props} foo={'override'} />; 
console.log(component.props.foo); // 'override'

自定義 HTML 屬性:

DOM 標(biāo)簽自定義屬性要使用 data- 前綴,這與 HTML 標(biāo)準(zhǔn)一致:
<div data-attr="xxx">content</div>

組件標(biāo)簽中任意屬性都是被支持的:
<x-my-component custom-attr="foo" />

1.2.2.4. JavaScript 屬性表達(dá)式

屬性值要使用表達(dá)式,即用 {} 代替 ""即可。
const person = <Person name={window.isLoggedIn ? window.name : ''} />;

子組件也可以使用表達(dá)式:
const content = <Container>{window.isLoggedIn ? <Nav /> : <Login />}</Container>;

1.2.2.5. HTML 轉(zhuǎn)義

React 會(huì)將所有要顯示到 DOM 的字符串轉(zhuǎn)義,防止 XSS。所以,如果 JSX 中含有轉(zhuǎn)義后的 實(shí)體字符,比如 ?(?),則后 DOM 中不會(huì)正確顯示,因?yàn)?React 自動(dòng)把 ? 中的特 殊字符轉(zhuǎn)義了。有幾種解決辦法:

直接使用 UTF-8 字符 ?;
使用對(duì)應(yīng)字符的 Unicode 編碼查詢編碼;
使用數(shù)組組裝 <div>{['cc ', <span>?</span>, ' 2015']}</div>;
直接插入原始的 HTML。

此外,React 提供了 dangerouslySetInnerHTML 屬性。正如其名,它的作用就是避免 React 轉(zhuǎn) 義字符,在確定必要的情況下可以使用它:
<div dangerouslySetInnerHTML={{__html: 'cc &copy; 2015'}} />

1.2.2.6. 事件監(jiān)聽(tīng)

React.js 不需要手動(dòng)調(diào)用 addEventListener 進(jìn)行監(jiān)聽(tīng),它幫我們封裝好了一系列的 on* 屬性,當(dāng)你要為某元素監(jiān)聽(tīng)某個(gè)事件時(shí),只需要簡(jiǎn)單地給它加上相應(yīng)的 on* 即可。你也不需要考慮不同瀏覽器兼容性問(wèn)題,React.js 已經(jīng)將其封裝好了。
注:on*只能用在普通的 HTML 標(biāo)簽上,而不能用在組件上。

event 對(duì)象
和普通瀏覽器一樣,事件監(jiān)聽(tīng)函數(shù)會(huì)自動(dòng)傳入一個(gè) event 對(duì)象,這個(gè)對(duì)象和普通而瀏覽器 event 對(duì)象所包含的方法和屬性基本一致。不同的是,這個(gè)對(duì)象并不是瀏覽器所提供的,而是 React.js 自己內(nèi)部構(gòu)建的。

        render(){
            const { isLiked } = this.state;
            const innerText = isLiked ? '取消' : '點(diǎn)贊';
            return(
                <button className='like-btn' onClick={this.isLikedChange} style={{background: this.props.color}}>
                    <span>{innerText}</span>
                </button>
            )
        }

關(guān)于事件中的 this
React.js 在調(diào)用你所傳給它的方法時(shí),并不是通過(guò)對(duì)象方法的方式調(diào)用,而是直接通過(guò)函數(shù)調(diào)用,所以事件監(jiān)聽(tīng)函數(shù)內(nèi)并不能通過(guò) this 獲取到當(dāng)前實(shí)例。如果你想在事件函數(shù)中使用當(dāng)前的實(shí)例,你需要手動(dòng)地將實(shí)例方法 bind 到當(dāng)前實(shí)例上再傳入給 React.js。

        render(){
            const { isLiked } = this.state;
            const innerText = isLiked ? '取消' : '點(diǎn)贊';
            return(
                <button className='like-btn' onClick={this.isLikedChange.bind(this)} style={{background: this.props.color}}>
                    <span>{innerText}</span>
                </button>
            )
        }

bind 會(huì)把實(shí)例方法綁定到當(dāng)前實(shí)例上,然后我們?cè)侔呀壎ê蟮暮瘮?shù)傳給 React.js 的 onClick 事件監(jiān)聽(tīng)。

1.3 React 組件

1.3.1 組件的演變

傳統(tǒng)組件:
組件封裝的基本思路就是面向?qū)ο笏枷?。交互基本上以操?DOM 為主,結(jié)構(gòu)上哪里需要變,就操作哪里。

基本的封裝性:通過(guò)實(shí)例化方法來(lái)構(gòu)造對(duì)象。
簡(jiǎn)單的生命周期呈現(xiàn):如 constructor 和 destroy 代表組建的掛載和卸載過(guò)程。
明確的數(shù)據(jù)流動(dòng):根據(jù)傳入?yún)?shù)的不同做出不同相應(yīng),從而得到渲染結(jié)果。
Web Components:


Web Components

1.3.2 React 組件的構(gòu)建

React 組件即為組件元素。組件元素被描述成純粹的 JSON 對(duì)象,意味著可以使用方法或類來(lái)構(gòu)建。
React 組件基本上由 3 個(gè)部分組成——屬性(props)、狀態(tài)(state)以及生命周期方法。如下圖:


React 組件

React 組件可以接收參數(shù)(props),也可能由自身狀態(tài)(state),接收到的參數(shù)或自身狀態(tài)有所改變,都會(huì)導(dǎo)致 React 組件執(zhí)行相應(yīng)的生命周期方法,最后渲染。

React 組件引用方式遵循 ES6 module 標(biāo)準(zhǔn)。

React 組件的構(gòu)建方法
官方在 React 組件構(gòu)建上提供了 3 種不同的方法:React.createClass、ES6 classes無(wú)狀態(tài)函數(shù)

1. React.createClass
用 React.createClass 構(gòu)建組件是 React 最傳統(tǒng)、兼容性最好的方法。

const Button = React.createClass({
    getDefaultProps() {
        return {
            color: 'blue',
            text: 'Confirm',
        };
    },
    render() {
        const { color, text } = this.props;
        return (
            <button className={`btn btn-${color}`}>
                <em>{text}</em>
            </button>
        );
    }
});

從表象看,React.createClass 方法就是構(gòu)建一個(gè)組件對(duì)象。另一個(gè)組件要調(diào)用 Button 組件時(shí),只需寫(xiě)成 <Button/>,就可以被解析成 React.createClass(Button) 方法來(lái)創(chuàng)建 Button 實(shí)例。

React.createClass會(huì)自綁定函數(shù)方法(不像React.Component只綁定需要關(guān)心的函數(shù))導(dǎo)致不必要的性能開(kāi)銷,增加代碼過(guò)時(shí)的可能性。

2. ES6 classes
ES6 classes 的寫(xiě)法是通過(guò) ES6 標(biāo)準(zhǔn)的類語(yǔ)法方式來(lái)構(gòu)建方法:


import React, { Component } from 'react';

class Button extends Component {
    constructor(props) {
        super(props);
    }
    static defaultProps = {
        color: 'blue',
        text: 'Confirm',
    };
    render() {
        const { color, text } = this.props;
        return (
            <button className={`btn btn-${color}`}>
                <em>{text}</em>
            </button>
        );
    }
}

這里的直觀感受是從調(diào)用內(nèi)部方法變成了用類來(lái)實(shí)現(xiàn)。與 createClass 的結(jié)果相同的是,調(diào)用類實(shí)現(xiàn)的組件會(huì)創(chuàng)建實(shí)例對(duì)象。

說(shuō)明:
React 的所有組件都繼承自頂層類 React.Component。它的定義非常簡(jiǎn)潔,只是初始化了React.Component方法,聲明了 props、context、refs 等,并在原型上定義了 setState 和forceUpdate 方法。內(nèi)部初始化的生命周期方法與 createClass 方式使用的是同一個(gè)方法創(chuàng)建的。

3. 無(wú)狀態(tài)函數(shù)
使用無(wú)狀態(tài)函數(shù)創(chuàng)建的組件成為無(wú)狀態(tài)組件,是官方頗為推崇的組件。

function Button({ color = 'blue', text = 'Confirm' }) {
    return (
        <button className={`btn btn-${color}`}>
            <em>{text}</em>
        </button>
    );
}

無(wú)狀態(tài)組件只傳入 props 和 context 兩個(gè)參數(shù),不存在 state,也沒(méi)有生命周期方法組件本身即是一個(gè) render 方法。不過(guò),像 propTypes 和 defaultProps 還是可以通過(guò)向方法設(shè)置靜態(tài)屬性來(lái)實(shí)現(xiàn)的。

注:只要有可能,盡量使用無(wú)狀態(tài)組件。

無(wú)狀態(tài)組件在調(diào)用時(shí)不會(huì)創(chuàng)建新實(shí)例,它創(chuàng)建時(shí)始終保持了一個(gè)實(shí)例,避免了不必要的檢查和內(nèi)存分配,做到了內(nèi)部?jī)?yōu)化。

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 原教程內(nèi)容詳見(jiàn)精益 React 學(xué)習(xí)指南,這只是我在學(xué)習(xí)過(guò)程中的一些閱讀筆記,個(gè)人覺(jué)得該教程講解深入淺出,比目前大...
    leonaxiong閱讀 2,954評(píng)論 1 18
  • HTML模版 之后出現(xiàn)的React代碼嵌套入模版中。 1. Hello world 這段代碼將一個(gè)一級(jí)標(biāo)題插入到指...
    ryanho84閱讀 6,449評(píng)論 0 9
  • 本筆記基于React官方文檔,當(dāng)前React版本號(hào)為15.4.0。 1. 安裝 1.1 嘗試 開(kāi)始之前可以先去co...
    Awey閱讀 7,936評(píng)論 14 128
  • 3. JSX JSX是對(duì)JavaScript語(yǔ)言的一個(gè)擴(kuò)展語(yǔ)法, 用于生產(chǎn)React“元素”,建議在描述UI的時(shí)候...
    pixels閱讀 2,983評(píng)論 0 24
  • http://android.lineten.net/layout.php
    Mr_Luo閱讀 169評(píng)論 0 0

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