超級(jí)給力的JavaScript的React框架入門教程
React 是 Facebook 里一群牛 X 的碼農(nóng)折騰出的牛X的框架。 實(shí)現(xiàn)了一個(gè)虛擬 DOM,用 DOM 的方式將需要的組件秒加,用不著的秒刪。React 扮演著 MVC 結(jié)構(gòu)中 V 的角色, 不過(guò)你要是 Flux 搭配使用, 你就有一個(gè)很牛X的能讓輕松讓 M 和 V 同步的框架了,F(xiàn)lux 的事以后再說(shuō)~
組件們
在 React 中,你可以創(chuàng)建一個(gè)有特殊功能的組件,這在 HTML 元素里你是打著燈籠也找不到的,比如這個(gè)教程里的下拉導(dǎo)航。每個(gè)組件都有自己的地盤(scope),所以我們定義一個(gè)組件后可以反復(fù)的用反復(fù)的用,根本就不需要和別的組件交互!
每個(gè)組件都有一個(gè)功能叫 render , 它可以高效的返回要射到瀏覽器上 HTML。我們也可以調(diào)用 React 的其他組件,這意味這一入 React 深似海,哦,不對(duì),是只有想不到,沒(méi)有做不到。
JSX
如果你經(jīng)常注意React你也許會(huì)發(fā)現(xiàn)有個(gè)東西叫JSX。JSX允許我們?cè)贘avascript中寫HTML,而不是用HTML包含Javascript。它能幫助我們快速開發(fā),因?yàn)槲覀儾挥脫?dān)心字符串和換行等。你可以在瀏覽器中運(yùn)行JSX,但是不推薦,因?yàn)檫@樣會(huì)減慢你的頁(yè)面速度。gulp和grunt為你的預(yù)處理任務(wù)提供了一個(gè)JSX解釋器,所以如果你想使用JSX,我建議開啟這個(gè)功能。
使用 JSX
如前面所說(shuō),JSX每一個(gè)組件都有一個(gè) render 方法,這個(gè)方法產(chǎn)生一個(gè)"ViewModel(視圖模型)" - 在返回HTML到組件之前,你可以將這個(gè)模型(ViewModel)的信息放入視圖中,意味著你的HTML會(huì)根據(jù)這個(gè)模型動(dòng)態(tài)改變(例如一個(gè)動(dòng)態(tài)列表)。
一旦你完成了操作,就可以返回你想渲染(render)的東西,我們使用JSX來(lái)做是如此簡(jiǎn)單
var ExampleComponent = React.createClass({
render: function () {
return (
<div className="navigation">
Hello World!
</div>
);
}
});
如果你要在你的瀏覽器中運(yùn)行這段代碼,只會(huì)得到語(yǔ)法錯(cuò)誤的提示,因?yàn)樵贘avascript中<和>字符要用引號(hào)包含起來(lái)。當(dāng)你在代碼上運(yùn)行JSX解釋器,它將被轉(zhuǎn)換成這樣
var ExampleComponent = React.createClass({
render: function () {
return (
React.createElement('div', {className: 'navigation'}, 'Hello World!')
);
}
});
可以點(diǎn)此測(cè)試示例 - 我使用的是瀏覽器JSX解釋器(這并不推薦,但是為了JSFiddle)。
運(yùn)行了!JSX解釋了你使用 React.createElement 生成的所有DOM節(jié)點(diǎn),生成了節(jié)點(diǎn)類型、參數(shù)和內(nèi)容。你可以不用JSX,但這意味著你必須要手動(dòng)寫 React.createElement 以外的所有DOM節(jié)點(diǎn)。無(wú)數(shù)例子告訴我要使用JSX。
你一定很疑惑我為什么在DOM上使用 className 來(lái)代替 class。這是因?yàn)?class 是Javascript的保留詞。當(dāng)JSX解釋你的代碼時(shí)就會(huì)改變這節(jié)點(diǎn)上的所有屬性到一個(gè)對(duì)象上,但你不能把對(duì)象當(dāng)作屬性!
將變量用在屬性上
如果你想動(dòng)態(tài)改變組件的樣式類(或者任何其他的屬性值),你可以使用變量. 不過(guò)你不能只是傳進(jìn)去一個(gè)變量名稱,你還要將其用一對(duì)大括弧包起來(lái), 這樣JSX就能知道這是一個(gè)外部的變量了.
var ExampleComponent = React.createClass({
render: function () {
var navigationClass = 'navigation';
return (
<div className={ navigationClass }>
Hello World!
</div>
);
}
});
你可以在這兒看到這個(gè)功能.
初始的渲染器
當(dāng)你最開始要渲染一個(gè)React組件時(shí),你需要告訴React是要渲染什么組件,還要制定一個(gè)現(xiàn)有的DOM節(jié)點(diǎn)以表示在哪兒渲染這個(gè)組件. 為此你會(huì)要使用React.render函數(shù).
var ExampleComponent = React.createClass({
render: function () {
return (
<div className="navigation">
Hello World!
</div>
);
}
});
React.render(<ExampleContent />, document.body);
他將會(huì)在body節(jié)點(diǎn)上渲染組件——簡(jiǎn)單吧! 從此你就可以像通常那樣調(diào)用其他的組件了, 或者如果你希望的話,可以使用render函數(shù)許多次, 如果你不想使用React來(lái)渲染整個(gè)頁(yè)面,但仍然想要使用多個(gè)組件.
一個(gè)組件的基礎(chǔ)
組件可以擁有其自己的“狀態(tài)”。這使我們能夠重復(fù)多次使用相同的組件但卻讓它們看起來(lái)完全不同,因?yàn)槊總€(gè)組件實(shí)例的狀態(tài)是唯一的。
當(dāng)你通過(guò)傳遞屬性到一個(gè)組件時(shí)這些被稱為屬性。不要只局限于 HTML 屬性,你可以傳遞任何你想傳遞的東西,并在組件內(nèi)通過(guò) this.props 訪問(wèn)它。這樣使得我們能夠重復(fù)使用相同的組件但傳遞一組不同的屬性,比如組件的“配置”。
屬性
根據(jù)我們前面“Hello World!”的例子,我們?cè)?HTML 節(jié)點(diǎn)上有 className 的屬性。在組件內(nèi)部,我們可以使用 this.props.classname 訪問(wèn)這個(gè)值,但正如前面所述,你可以傳遞任何你喜歡的內(nèi)容。對(duì)我們的下拉來(lái)說(shuō),我們需要將導(dǎo)航配置為對(duì)象,組件將使用它作為要渲染的配置。讓我們開始吧—
var navigationConfig = [];
var Navigation = React.createClass({
render: function () {
return (
<div className="navigation">
</div>
);
}
});
React.render(<Navigation config={ navigationConfig } />, document.body);
如果現(xiàn)在能訪問(wèn) this.props.config 的話,我們會(huì)受到一個(gè)空數(shù)組(navigationConfig 的值)。在我們進(jìn)入到真正導(dǎo)航的編碼前先讓我們說(shuō)明一下?tīng)顟B(tài)。
狀態(tài)
如之前所討論的,每一個(gè)組件都有其自己的”狀態(tài)”。當(dāng)要使用狀態(tài)時(shí),你要定義初始狀態(tài),讓后才可以使用 this.setState 來(lái)更新?tīng)顟B(tài)。無(wú)論何時(shí)狀態(tài)得到了更新,組件都會(huì)再一次調(diào)用 render 函數(shù),拿新的值去替換或者改變之前渲染的值。這就是虛擬 DOM 的奧義 - 計(jì)算差異的算法實(shí)在 React 內(nèi)部完成的,因此我們不用去以來(lái) DOM 的更新了(因?yàn)?DOM 很慢)。React 會(huì)計(jì)算出差異并產(chǎn)生一堆指令的集合 (例如,加入向”navigation__link“加入”active“類,或者移除一個(gè)節(jié)點(diǎn)),并在 DOM 上執(zhí)行它們。
使用導(dǎo)航,我們將下拉菜單打開保持在狀態(tài)中。為此,添加一個(gè) getInitialState 函數(shù)到類配置對(duì)象上,并返回帶有我們想要的初始狀態(tài)的對(duì)象。
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);
你會(huì)發(fā)現(xiàn)我們將其設(shè)置成了-1。當(dāng)我們準(zhǔn)備去打開一個(gè)下拉菜單時(shí),將使用狀態(tài)里面的配置數(shù)組中的導(dǎo)航項(xiàng)的位置,而由于數(shù)組索引開始于0,我們就得使用 -1 來(lái)表示還沒(méi)有指向一個(gè)導(dǎo)航項(xiàng)。
我們可以使用 this.state 來(lái)訪問(wèn)狀態(tài),因此如果我們?nèi)ビ^察 atthis.state.openDropdown,應(yīng)該會(huì)有 -1 被返回。
組件的生命周期
每個(gè)組件都有其“生命周期”—這是一系列你可以在組件配置里定義的函數(shù),它們將在部件生命周期內(nèi)被調(diào)用。我們已經(jīng)看過(guò)了 getinitialstate 一它只被調(diào)用一次,在組件被掛載時(shí)調(diào)用。
componentWillMount
當(dāng)組件要被掛載時(shí)這個(gè)函數(shù)被調(diào)用。這意味著我們可以在此運(yùn)行組件功能必須的代碼。為由于 render 在組件生命周期里被多次調(diào)用,我們一般會(huì)把只需要執(zhí)行一次的代碼放在這里,比如 XHR 請(qǐng)求。
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
一旦你的組件已經(jīng)運(yùn)行了 render 函數(shù),并實(shí)際將組件渲染到了 DOM 中,componentDidMount 就會(huì)被調(diào)用。我們可以在這兒做任何需要做的 DOM 操作,已經(jīng)任何需要依賴于組件已經(jīng)實(shí)際存在于 DOM 之后才能做的事情, 例如渲染一個(gè)圖表。你可以通過(guò)調(diào)用 this.getDOMNode 在內(nèi)部訪問(wèn)到 DOM 節(jié)點(diǎn)。
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
如果你準(zhǔn)備吧組件從 DOM 移除時(shí),這個(gè)函數(shù)將會(huì)被調(diào)用。這讓我們可以在組件背后進(jìn)行清理,比如移除任何我們已經(jīng)綁定的事件監(jiān)聽(tīng)器。如果我們沒(méi)有在自身背后做清理,而當(dāng)其中一個(gè)事件被觸發(fā)時(shí),就會(huì)嘗試去計(jì)算一個(gè)沒(méi)有載入的組件,React 就會(huì)拋出一個(gè)錯(cuò)誤。
var ExampleComponent = React.createClass({
componentWillUnmount: function () {
// unbind any event listeners specific to this component
},
render: function () {
return (
<div>
Hello World!
</div>
);
}
});
組件方法
React 也為組件提供了更方便我們工作的方法。它們會(huì)在組件的創(chuàng)建過(guò)程中被調(diào)用到。例如getInitialState,能讓我們定義一個(gè)默認(rèn)的狀態(tài),這樣我們不用擔(dān)心在代碼里面對(duì)狀態(tài)項(xiàng)目是否存在做進(jìn)一步的檢查了。
getDefaultProps
當(dāng)我們創(chuàng)建組件時(shí),可以按照我們的想法為組件的屬性定義默認(rèn)值。這意味著我們?cè)谡{(diào)用組件時(shí),如果給這些屬性設(shè)置值,組件會(huì)有一個(gè)默認(rèn)的“配置”,而我們就不用操心在下一行代碼中檢查屬性這類事情了。
當(dāng)你定義了組件時(shí),這些默認(rèn)的屬性會(huì)被緩存起來(lái),因而針對(duì)每個(gè)組件的實(shí)例它們都是一樣的,并且不能被改變。對(duì)于導(dǎo)航組件,我們將配置指定為一個(gè)空的數(shù)組,因而就算沒(méi)有傳入配置,render 方法內(nèi)也不會(huì)發(fā)生錯(cuò)誤。
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
我們也可以隨意為每一個(gè)屬性指定類型。這對(duì)于我們檢查和處理屬性的意外賦值非常有用。如下面的dropdown,我們指定只有數(shù)組才能放入配置。
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。這是一個(gè)獨(dú)立于 React 的基本組件(只是一個(gè)對(duì)象類型的配置)。這意味著,如果我們有兩個(gè)功能類似的組件,就可以共享一個(gè)配置了(如果初始狀態(tài)相同)。我們可以抽象化地在 mixin 中建立一個(gè)方法,這樣就不用把相同的代碼寫上兩次了。
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 方法了,保存我們重寫的代碼。無(wú)論如何,你不能 override(覆蓋)這些屬性,如果這個(gè)屬性是在mixin里設(shè)置的,它在這個(gè)組件中是不可覆蓋的。
遍歷循環(huán)
當(dāng)我們有一個(gè)包含對(duì)象的數(shù)組,如何循環(huán)這個(gè)數(shù)組并渲染每一個(gè)對(duì)象到列表項(xiàng)中?JSX 允許你在任意 Javascript 文件中使用它,你可以映射這個(gè)數(shù)組并返回 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
導(dǎo)航配置由數(shù)組和對(duì)象組成,包括一個(gè)指向超鏈接的 href 屬性和一個(gè)用于顯示的 text 屬性。當(dāng)我們映射的時(shí)候,它會(huì)一個(gè)個(gè)依次通過(guò)這個(gè)數(shù)組去取得對(duì)象。我們可以訪問(wèn) href 和 text,并在 HTML 中使用。當(dāng)返回列表時(shí),這個(gè)數(shù)組里的列表項(xiàng)都將被替換,所以我們把它放入 React 中處理時(shí)它將知道怎么去渲染了!
混編
到目前位置,我們已經(jīng)做到了所有下拉列表的展開。我們需要知道被下來(lái)的項(xiàng)目是哪個(gè),我們將使用 .children 屬性去遍歷我們的 navigationConfig 數(shù)組。接下來(lái),我們可以通過(guò)循環(huán)來(lái)操作下拉的子元素條目。
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);
實(shí)例在這里 - 但是我們還是能看見(jiàn)下來(lái)?xiàng)l目,盡管我們將 openDropdown 設(shè)置成為了 -1 。
我們可以通過(guò)在組件中訪問(wèn) this.state ,來(lái)判斷下拉是否被打開了,并且,我們可以為其添加一個(gè)新的 css 樣式 class 來(lái)展現(xiàn)鼠標(biāo) 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);
在這里看實(shí)例 - 鼠標(biāo)劃過(guò)“My Website”,下拉即會(huì)展現(xiàn)。
在前面,我已經(jīng)給每個(gè)列表項(xiàng)添加了鼠標(biāo)事件。如你所見(jiàn),我用的是 .bind (綁定) 調(diào)用,而非其它的方式調(diào)用 - 這是因?yàn)?,?dāng)用戶的鼠標(biāo)劃出元素區(qū)域,我們并不用關(guān)注光標(biāo)在哪里,所有我們需要知曉的是,將下拉關(guān)閉掉,所以我們可以將它的值設(shè)置成為-1。但是,我們需要知曉的是當(dāng)用戶鼠標(biāo)劃入的時(shí)候哪個(gè)元素被下拉展開了,所以我們需要知道該參數(shù)(元素的索引)。 我們使用綁定的方式去調(diào)用而非簡(jiǎn)單的透過(guò)函數(shù)(function)去調(diào)用是因?yàn)槲覀冃枰ㄟ^(guò) React 去調(diào)用。如果我們直接調(diào)用,那我們就需要一直調(diào)用,而不是在事件中調(diào)用他。
現(xiàn)在我們可以添加很多的條目到 navigationConfig 當(dāng)中,而且我們也可以給他添加樣式到下來(lái)功能當(dāng)中。查看實(shí)例.
相關(guān)文章
Underscore.js 1.3.3 中文注釋翻譯說(shuō)明
Underscore一個(gè)JavaScript實(shí)用庫(kù),提供了一整套函數(shù)式編程的實(shí)用功能,但是沒(méi)有擴(kuò)展任何JavaScript內(nèi)置對(duì)象,本文就翻譯了它的源代碼中的注釋,需要的朋友可以參考下2015-06-06
JavaScript高級(jí)程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記3 js簡(jiǎn)單數(shù)據(jù)類型
數(shù)據(jù)類型是編程語(yǔ)言的磚瓦,是所有你能想象到的復(fù)雜抽象的基礎(chǔ),在現(xiàn)代編程語(yǔ)言中,除了語(yǔ)言本身內(nèi)置的一些簡(jiǎn)單數(shù)據(jù)類型外,基本上都提供了用于自定義數(shù)據(jù)類型的語(yǔ)言機(jī)制(在C中也可以利用結(jié)構(gòu)體來(lái)實(shí)現(xiàn)),這些機(jī)制在一定程度上也決定了該語(yǔ)言的流行度和生命力2012-10-10
JavaScript中的Math.atan2()方法使用詳解
這篇文章主要介紹了JavaScript中的Math.atan2()方法使用詳解,是JS入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-06-06
JavaScript展開操作符(Spread operator)詳解
在本篇文章里小編給大家整理的是關(guān)于JavaScript展開操作符(Spread operator)的詳細(xì)介紹以及用法,需要的讀者們參考下。2019-07-07
javascript定義變量時(shí)有var和沒(méi)有var的區(qū)別探討
定義變量時(shí)省略var是不安全的,不過(guò)是合法的。定義的變量的作用域取決于定義的位置2014-07-07
JavaScript中用let語(yǔ)句聲明作用域的用法講解
首先要注意let是ES6中的東西,起碼是IE10之前的IE瀏覽器兼容要千萬(wàn)當(dāng)心!嗯...然后我們來(lái)看JavaScript中用let語(yǔ)句聲明作用域的用法講解2016-05-05

