React 入門教程

oschina發表於2015-06-15

React 是 Facebook 裡一群牛 X 的碼農折騰出的牛X的框架。 實現了一個虛擬 DOM,用 DOM 的方式將需要的元件秒加,用不著的秒刪。React 扮演著 MVC 結構中 V 的角色, 不過你要是 Flux 搭配使用, 你就有一個很牛X的能讓輕鬆讓 M 和 V 同步的框架了,Flux 的事以後再說~

元件們

在 React 中,你可以建立一個有特殊功能的元件,這在 HTML 元素裡你是打著燈籠也找不到的,比如這個教程裡的下拉導航。每個元件都有自己的地盤(scope),所以我們定義一個元件後可以反覆的用反覆的用,根本就不需要和別的元件互動!

每個元件都有一個功能叫 render , 它可以高效的返回要射到瀏覽器上 HTML。我們也可以呼叫 React 的其他元件,這意味這一入 React 深似海,哦,不對,是隻有想不到,沒有做不到。

JSX

如果你經常注意React你也許會發現有個東西叫JSX。JSX允許我們在Javascript中寫HTML,而不是用HTML包含Javascript。它能幫助我們快速開發,因為我們不用擔心字串和換行等。你可以在瀏覽器中執行JSX,但是不推薦,因為這樣會減慢你的頁面速度。gulp和grunt為你的預處理任務提供了一個JSX直譯器,所以如果你想使用JSX,我建議開啟這個功能。

使用 JSX

如前面所說,JSX每一個元件都有一個 render 方法,這個方法產生一個”ViewModel(檢視模型)” – 在返回HTML到元件之前,你可以將這個模型(ViewModel)的資訊放入檢視中,意味著你的HTML會根據這個模型動態改變(例如一個動態列表)。

一旦你完成了操作,就可以返回你想渲染(render)的東西,我們使用JSX來做是如此簡單

var ExampleComponent = React.createClass({
    render: function () {
        return (
            <div className="navigation">
                Hello World!
            </div>
            );
    }
});

如果你要在你的瀏覽器中執行這段程式碼,只會得到語法錯誤的提示,因為在Javascript中<和>字元要用引號包含起來。當你在程式碼上執行JSX直譯器,它將被轉換成這樣

var ExampleComponent = React.createClass({
    render: function () {
        return (
            React.createElement('div', {className: 'navigation'}, 'Hello World!')
            );
    }
});

可以點此測試示例 - 我使用的是瀏覽器JSX直譯器(這並不推薦,但是為了JSFiddle)。

執行了!JSX解釋了你使用 React.createElement 生成的所有DOM節點,生成了節點型別、引數和內容。你可以不用JSX,但這意味著你必須要手動寫 React.createElement 以外的所有DOM節點。無數例子告訴我要使用JSX。

你一定很疑惑我為什麼在DOM上使用 className 來代替 class。這是因為 class 是Javascript的保留詞。當JSX解釋你的程式碼時就會改變這節點上的所有屬性到一個物件上,但你不能把物件當作屬性!

將變數用在屬性上

如果你想動態改變元件的樣式類(或者任何其他的屬性值),你可以使用變數. 不過你不能只是傳進去一個變數名稱,你還要將其用一對大括弧包起來, 這樣JSX就能知道這是一個外部的變數了.

var ExampleComponent = React.createClass({
render: function () {
    var navigationClass = 'navigation';
    return (
        <div className={ navigationClass }>
            Hello World!
        </div>
        );
}
});

你可以在這兒看到這個功能.

初始的渲染器

當你最開始要渲染一個React元件時,你需要告訴React是要渲染什麼元件,還要制定一個現有的DOM節點以表示在哪兒渲染這個元件. 為此你會要使用React.render函式.

var ExampleComponent = React.createClass({
render: function () {
    return (
        <div className="navigation">
            Hello World!
        </div>
        );
}
});
React.render(<ExampleContent />, document.body);

他將會在body節點上渲染元件——簡單吧! 從此你就可以像通常那樣呼叫其他的元件了, 或者如果你希望的話,可以使用render函式許多次, 如果你不想使用React來渲染整個頁面,但仍然想要使用多個元件.

一個元件的基礎

元件可以擁有其自己的“狀態”。這使我們能夠重複多次使用相同的元件但卻讓它們看起來完全不同,因為每個元件例項的狀態是唯一的。

當你通過傳遞屬性到一個元件時這些被稱為屬性。不要只侷限於 HTML 屬性,你可以傳遞任何你想傳遞的東西,並在元件內通過 this.props 訪問它。這樣使得我們能夠重複使用相同的元件但傳遞一組不同的屬性,比如元件的“配置”。

屬性

根據我們前面“Hello World!”的例子,我們在 HTML 節點上有 className 的屬性。在元件內部,我們可以使用 this.props.classname 訪問這個值,但正如前面所述,你可以傳遞任何你喜歡的內容。對我們的下拉來說,我們需要將導航配置為物件,元件將使用它作為要渲染的配置。讓我們開始吧—

var navigationConfig = [];

var Navigation = React.createClass({
    render: function () {
        return (
            <div className="navigation">

            </div>
            );
    }
});

React.render(<Navigation config={ navigationConfig } />, document.body);

如果現在能訪問 this.props.config 的話,我們會受到一個空陣列(navigationConfig 的值)。在我們進入到真正導航的編碼前先讓我們說明一下狀態。

狀態

如之前所討論的,每一個元件都有其自己的”狀態”。當要使用狀態時,你要定義初始狀態,讓後才可以使用 this.setState 來更新狀態。無論何時狀態得到了更新,元件都會再一次呼叫 render 函式,拿新的值去替換或者改變之前渲染的值。這就是虛擬 DOM 的奧義 – 計算差異的演算法實在 React 內部完成的,因此我們不用去以來 DOM 的更新了(因為 DOM 很慢)。React 會計算出差異併產生一堆指令的集合 (例如,加入向”navigation__link“加入”active“類,或者移除一個節點),並在 DOM 上執行它們。

使用導航,我們將下拉選單開啟保持在狀態中。為此,新增一個 getInitialState 函式到類配置物件上,並返回帶有我們想要的初始狀態的物件。

var navigationConfig = [];

var Navigation = React.createClass({
    getInitialState: function () {
        return {
            openDropdown: -1
        };
    },
    render: function () {
        return (
            <div className="navigation">

            </div>
            );
    }
});

React.render(<Navigation config={ navigationConfig } />, document.body);

你會發現我們將其設定成了-1。當我們準備去開啟一個下拉選單時,將使用狀態裡面的配置陣列中的導航項的位置,而由於陣列索引開始於0,我們就得使用 -1 來表示還沒有指向一個導航項。

我們可以使用 this.state 來訪問狀態,因此如果我們去觀察 atthis.state.openDropdown,應該會有 -1 被返回。

元件的生命週期

每個元件都有其“生命週期”—這是一系列你可以在元件配置裡定義的函式,它們將在部件生命週期內被呼叫。我們已經看過了 getinitialstate 一它只被呼叫一次,在元件被掛載時呼叫。

componentWillMount

當元件要被掛載時這個函式被呼叫。這意味著我們可以在此執行元件功能必須的程式碼。為由於 render 在元件生命週期裡被多次呼叫,我們一般會把只需要執行一次的程式碼放在這裡,比如 XHR 請求。

var ExampleComponent = React.createClass({
    componentWillMount: function () {
        // xhr request here to get data
    },
    render: function () {
        // this gets called many times in a components life
        return (
            <div>
                Hello World!
            </div>
            );
    }
});

componentDidMount

一旦你的元件已經執行了 render 函式,並實際將元件渲染到了 DOM 中,componentDidMount 就會被呼叫。我們可以在這兒做任何需要做的 DOM 操作,已經任何需要依賴於元件已經實際存在於 DOM 之後才能做的事情, 例如渲染一個圖表。你可以通過呼叫 this.getDOMNode 在內部訪問到 DOM 節點。

var ExampleComponent = React.createClass({
    componentDidMount: function () {
        var node = this.getDOMNode();
        // render a chart on the DOM node
    },
    render: function () {
        return (
            <div>
                Hello World!
            </div>
            );
    }
});

componentWillUnmount

如果你準備吧元件從 DOM 移除時,這個函式將會被呼叫。這讓我們可以在元件背後進行清理,比如移除任何我們已經繫結的事件監聽器。如果我們沒有在自身背後做清理,而當其中一個事件被觸發時,就會嘗試去計算一個沒有載入的元件,React 就會丟擲一個錯誤。

var ExampleComponent = React.createClass({
    componentWillUnmount: function () {
        // unbind any event listeners specific to this component
    },
    render: function () {
        return (
            <div>
                Hello World!
            </div>
            );
    }
});

元件方法

React 也為元件提供了更方便我們工作的方法。它們會在元件的建立過程中被呼叫到。例如getInitialState,能讓我們定義一個預設的狀態,這樣我們不用擔心在程式碼裡面對狀態專案是否存在做進一步的檢查了。

getDefaultProps

當我們建立元件時,可以按照我們的想法為元件的屬性定義預設值。這意味著我們在呼叫元件時,如果給這些屬性設定值,元件會有一個預設的“配置”,而我們就不用操心在下一行程式碼中檢查屬性這類事情了。

當你定義了元件時,這些預設的屬性會被快取起來,因而針對每個元件的例項它們都是一樣的,並且不能被改變。對於導航元件,我們將配置指定為一個空的陣列,因而就算沒有傳入配置,render 方法內也不會發生錯誤。

var Navigation = React.createClass({
    getInitialState: function () {
        return {
            openDropdown: -1
        };
    },
    getDefaultProps: function () {
        return {
            config: []
        }
    },
    render: function () {
        return (
            <div className="navigation">

            </div>
            );
    }
});

React.render(<Navigation config={ navigationConfig } />, document.body);

propTypes

我們也可以隨意為每一個屬性指定型別。這對於我們檢查和處理屬性的意外賦值非常有用。如下面的dropdown,我們指定只有陣列才能放入配置。

var Navigation = React.createClass({
    getInitialState: function () {
        return {
            openDropdown: -1
        };
    },
    getDefaultProps: function () {
        return {
            config: []
        }
    },
    propTypes: {
        config: React.PropTypes.array
    },
    render: function () {
        return (
            <div className="navigation">

            </div>
            );
    }
});

React.render(<Navigation config={ navigationConfig } />, document.body);

mixins

我們也可以在元件中加入 mixins。這是一個獨立於 React 的基本元件(只是一個物件型別的配置)。這意味著,如果我們有兩個功能類似的元件,就可以共享一個配置了(如果初始狀態相同)。我們可以抽象化地在 mixin 中建立一個方法,這樣就不用把相同的程式碼寫上兩次了。

var ExampleMixin = {
    componentDidMount: function () {
        // bind some event listeners here
    },
    componentWillUnmount: function () {
        // unbind those events here!
    }
};

var ExampleComponent = React.createClass({
    mixins: [ExampleMixin],
    render: function () {
        return (
            <div>
                Hello World!
            </div>
            );
    }
});

var AnotherComponent = React.createClass({
    mixins: [ExampleMixin],
    render: function () {
        return (
            <div>
                Hello World!
            </div>
            );
    }
});

這樣全部元件都有一樣的 componentDidMount 和 componentWillUnmount 方法了,儲存我們重寫的程式碼。無論如何,你不能 override(覆蓋)這些屬性,如果這個屬性是在mixin裡設定的,它在這個元件中是不可覆蓋的。

遍歷迴圈

當我們有一個包含物件的陣列,如何迴圈這個陣列並渲染每一個物件到列表項中?JSX 允許你在任意 Javascript 檔案中使用它,你可以對映這個陣列並返回 JSX,然後使用 React 去渲染。

var navigationConfig = [
    {
        href: 'http://ryanclark.me',
        text: 'My Website'
    }
];
var Navigation = React.createClass({
    getInitialState: function () {
        return {
            openDropdown: -1
        };
    },
    getDefaultProps: function () {
        return {
            config: []
        }
    },
    propTypes: {
        config: React.PropTypes.array
    },
    render: function () {
        var config = this.props.config;
        var items = config.map(function (item) {
            return (
                <li className="navigation__item">
                    <a className="navigation__link" href={ item.href }>
                        { item.text }
                    </a>
                </li>
                );
        });
        return (
            <div className="navigation">
                { items }
            </div>
            );
    }
});
React.render(<Navigation config={ navigationConfig } />, document.body);

在 JSFilddle 中隨意使用 navigationConfigin

導航配置由陣列和物件組成,包括一個指向超連結的 href 屬性和一個用於顯示的 text 屬性。當我們對映的時候,它會一個個依次通過這個陣列去取得物件。我們可以訪問 href 和 text,並在 HTML 中使用。當返回列表時,這個陣列裡的列表項都將被替換,所以我們把它放入 React 中處理時它將知道怎麼去渲染了!

混編

到目前位置,我們已經做到了所有下拉選單的展開。我們需要知道被下來的專案是哪個,我們將使用 .children 屬性去遍歷我們的 navigationConfig 陣列。接下來,我們可以通過迴圈來操作下拉的子元素條目。

var navigationConfig = [
    {
        href: 'http://ryanclark.me',
        text: 'My Website',
        children: [
            {
                href: 'http://ryanclark.me/how-angularjs-implements-dirty-checking/',
                text: 'Angular Dirty Checking'
            },
            {
                href: 'http://ryanclark.me/getting-started-with-react/',
                text: 'React'
            }
        ]
    }
];
var Navigation = React.createClass({
    getInitialState: function () {
        return {
            openDropdown: -1
        };
    },
    getDefaultProps: function () {
        return {
            config: []
        }
    },
    propTypes: {
        config: React.PropTypes.array
    },
    render: function () {
        var config = this.props.config;
        var items = config.map(function (item) {
            var children, dropdown;
            if (item.children) {
                children = item.children.map(function (child) {
                    return (
                        <li className="navigation__dropdown__item">
                            <a className="navigation__dropdown__link" href={ child.href }>
                                { child.text }
                            </a>
                        </li>
                    );
                });
                dropdown = (
                    <ul className="navigation__dropdown">
                        { children }
                    </ul>
                );
            }
            return (
                <li className="navigation__item">
                    <a className="navigation__link" href={ item.href }>
                        { item.text }
                    </a>
                    { dropdown }
                </li>
                );
        });
        return (
            <div className="navigation">
                { items }
            </div>
            );
    }
});
React.render(<Navigation config={ navigationConfig } />, document.body);

例項在這裡 - 但是我們還是能看見下來條目,儘管我們將 openDropdown 設定成為了 -1 。

我們可以通過在元件中訪問 this.state ,來判斷下拉是否被開啟了,並且,我們可以為其新增一個新的 css 樣式 class 來展現滑鼠 hover 的效果。

var navigationConfig = [
    {
        href: 'http://ryanclark.me',
        text: 'My Website',
        children: [
            {
                href: 'http://ryanclark.me/how-angularjs-implements-dirty-checking/',
                text: 'Angular Dirty Checking'
            },
            {
                href: 'http://ryanclark.me/getting-started-with-react/',
                text: 'React'
            }
        ]
    }
];
var Navigation = React.createClass({
    getInitialState: function () {
        return {
            openDropdown: -1
        };
    },
    getDefaultProps: function () {
        return {
            config: []
        }
    },
    openDropdown: function (id) {
        this.setState({
            openDropdown: id
        });
    },
    closeDropdown: function () {
        this.setState({
            openDropdown: -1
        });
    },
    propTypes: {
        config: React.PropTypes.array
    },
    render: function () {
        var config = this.props.config;
        var items = config.map(function (item, index) {
            var children, dropdown;
            if (item.children) {
                children = item.children.map(function (child) {
                    return (
                        <li className="navigation__dropdown__item">
                            <a className="navigation__dropdown__link" href={ child.href }>
                                { child.text }
                            </a>
                        </li>
                    );
                });
                var dropdownClass = 'navigation__dropdown';
                if (this.state.openDropdown === index) {
                    dropdownClass += ' navigation__dropdown--open';
                }
                console.log(this.state.openDropdown, index);
                dropdown = (
                    <ul className={ dropdownClass }>
                        { children }
                    </ul>
                );
            }
            return (
                <li className="navigation__item" onMouseOut={ this.closeDropdown } onMouseOver={ this.openDropdown.bind(this, index) }>
                    <a className="navigation__link" href={ item.href }>
                        { item.text }
                    </a>
                    { dropdown }
                </li>
                );
        }, this);
        return (
            <div className="navigation">
                { items }
            </div>
            );
    }
});
React.render(<Navigation config={ navigationConfig } />, document.body);

在這裡看例項 – 滑鼠劃過“My Website”,下拉即會展現。

在前面,我已經給每個列表項新增了滑鼠事件。如你所見,我用的是 .bind (繫結) 呼叫,而非其它的方式呼叫 – 這是因為,當使用者的滑鼠劃出元素區域,我們並不用關注游標在哪裡,所有我們需要知曉的是,將下拉關閉掉,所以我們可以將它的值設定成為-1。但是,我們需要知曉的是當使用者滑鼠劃入的時候哪個元素被下拉展開了,所以我們需要知道該引數(元素的索引)。 我們使用繫結的方式去呼叫而非簡單的透過函式(function)去呼叫是因為我們需要通過 React 去呼叫。如果我們直接呼叫,那我們就需要一直呼叫,而不是在事件中呼叫他。

現在我們可以新增很多的條目到 navigationConfig 當中,而且我們也可以給他新增樣式到下來功能當中。檢視例項.

總結

React 是一個簡潔方便易用而又強大的框架。它簡潔的 API,可以讓人快速的建立自己的元件,而不去理會複雜的 dom 也不用做某些特別負責的操作,特別是在重複的列表項等上面表現不錯。有任何問題,想和我約,請關注我的 twitter @rynclark。感謝閱讀。

相關文章