1 安裝
????????React 的安裝包,可以到官網(wǎng)下載呐芥。不過(guò),React Demos已經(jīng)自帶 React源碼奋岁,不用另外安裝思瘟,只需把這個(gè)庫(kù)拷貝到你的硬盤就行了。
$ git clonegit@github.com:ruanyf/react-demos.git
????????如果你沒(méi)安裝 git闻伶, 那就直接下載 zip 壓縮包滨攻。
????????下面要講解的12個(gè)例子在各個(gè) Demo 子目錄,每個(gè)目錄都有一個(gè) index.html 文件,在瀏覽器打開(kāi)這個(gè)文件(大多數(shù)情況下雙擊即可)光绕,就能立刻看到效果女嘲。
????????需要說(shuō)明的是,React 可以在瀏覽器運(yùn)行诞帐,也可以在服務(wù)器運(yùn)行欣尼,但是本教程只涉及瀏覽器。一方面是為了盡量保持簡(jiǎn)單景埃,另一方面 React 的語(yǔ)法是一致的,服務(wù)器的用法與瀏覽器差別不大顶别。Demo13是服務(wù)器首屏渲染的例子谷徙,有興趣的朋友可以自己去看源碼。
2 HTML模板
????????使用 React 的網(wǎng)頁(yè)源碼驯绎,結(jié)構(gòu)大致如下完慧。
????????上面代碼有兩個(gè)地方需要注意。首先剩失,最后一個(gè) 標(biāo)簽的 type 屬性為 text/babel 屈尼。這是因?yàn)?React 獨(dú)有的 JSX 語(yǔ)法,跟JavaScript 不兼容拴孤。凡是使用 JSX 的地方脾歧,都要加上 type="text/babel" 。
????????其次演熟,上面代碼一共用了三個(gè)庫(kù): react.js 鞭执、react-dom.js 和 Browser.js ,它們必須首先加載芒粹。其中兄纺,react.js 是 React 的核心庫(kù),react-dom.js 是提供與 DOM 相關(guān)的功能化漆,Browser.js 的作用是將 JSX 語(yǔ)法轉(zhuǎn)為JavaScript 語(yǔ)法估脆,這一步很消耗時(shí)間,實(shí)際上線的時(shí)候座云,應(yīng)該將它放到服務(wù)器完成疙赠。
$ babel src --out-dir build
????????上面命令可以將 src 子目錄的 js 文件進(jìn)行語(yǔ)法轉(zhuǎn)換,轉(zhuǎn)碼后的文件全部放在 build 子目錄朦拖。
3 ReactDOM.render()
????????ReactDOM.render 是 React 的最基本方法棺聊,用于將模板轉(zhuǎn)為 HTML 語(yǔ)言,并插入指定的 DOM 節(jié)點(diǎn)贞谓。
ReactDOM.render(<h1>Hello, world!</h1>, document.getElementById('example'));
????????上面代碼將一個(gè) h1 標(biāo)題限佩,插入 example 節(jié)點(diǎn)(查看 demo01),運(yùn)行結(jié)果如下。
4 JSX語(yǔ)法
????????上一節(jié)的代碼祟同, HTML 語(yǔ)言直接寫在JavaScript 語(yǔ)言之中作喘,不加任何引號(hào),這就是 JSX 的語(yǔ)法晕城,它允許 HTML 與 JavaScript 的混寫(查看 Demo02)泞坦。
var names = ['Alice', 'Emily', 'Kate'];
ReactDOM.render(
<div>
? {
??? names.map(function (name) {
????? return <div>Hello, {name}!</div>
??? })
? }
? </div>,
? document.getElementById('example')
);
????????上面代碼體現(xiàn)了 JSX 的基本語(yǔ)法規(guī)則:遇到 HTML 標(biāo)簽(以 < 開(kāi)頭),就用 HTML 規(guī)則解析砖顷;遇到代碼塊(以 { 開(kāi)頭)贰锁,就用 JavaScript 規(guī)則解析。上面代碼的運(yùn)行結(jié)果如下滤蝠。
????????JSX 允許直接在模板插入 JavaScript 變量豌熄。如果這個(gè)變量是一個(gè)數(shù)組,則會(huì)展開(kāi)這個(gè)數(shù)組的所有成員(查看 demo03)物咳。
var arr = [
? <h1>Hello world!</h1>,
? <h2>React is awesome</h2>,
];
ReactDOM.render(
? <div>{arr}</div>,
? document.getElementById('example')
);
????上面代碼的arr變量是一個(gè)數(shù)組锣险,結(jié)果 JSX 會(huì)把它的所有成員,添加到模板览闰,運(yùn)行結(jié)果如下芯肤。
5 組件
????????React 允許將代碼封裝成組件(component),然后像插入普通 HTML 標(biāo)簽一樣压鉴,在網(wǎng)頁(yè)中插入這個(gè)組件崖咨。React.createClass 方法就用于生成一個(gè)組件類(查看 demo04)。
var HelloMessage = React.createClass({
? render: function() {
??? return <h1>Hello {this.props.name}</h1>;
? }
});
ReactDOM.render(
? <HelloMessage name="John" />,
? document.getElementById('example')
);
? ? ????上面代碼中油吭,變量 HelloMessage 就是一個(gè)組件類掩幢。模板插入<HelloMessage />? 時(shí),會(huì)自動(dòng)生成 HelloMessage 的一個(gè)實(shí)例(下文的"組件"都指組件類的實(shí)例)上鞠。所有組件類都必須有自己的 render 方法际邻,用于輸出組件。
????????注意芍阎,組件類的第一個(gè)字母必須大寫世曾,否則會(huì)報(bào)錯(cuò),比如HelloMessage不能寫成helloMessage谴咸。另外轮听,組件類只能包含一個(gè)頂層標(biāo)簽,否則也會(huì)報(bào)錯(cuò)岭佳。
var HelloMessage = React.createClass({
? render: function() {
??? return <h1>
????? Hello{this.props.name}
????????</h1>
????????<p>
????? ????some text
????????</p>;
? }
});
????????上面代碼會(huì)報(bào)錯(cuò)血巍,因?yàn)镠elloMessage組件包含了兩個(gè)頂層標(biāo)簽:h1和p。
????????組件的用法與原生的 HTML 標(biāo)簽完全一致珊随,可以任意加入屬性述寡,比如<HelloMessage name="John">? 柿隙,就是 HelloMessage 組件加入一個(gè) name 屬性,值為 John鲫凶。組件的屬性可以在組件類的 this.props 對(duì)象上獲取禀崖,比如 name 屬性就可以通過(guò) this.props.name 讀取。上面代碼的運(yùn)行結(jié)果如下螟炫。
????????添加組件屬性波附,有一個(gè)地方需要注意,就是 class 屬性需要寫成 className 昼钻,for 屬性需要寫成 htmlFor 掸屡,這是因?yàn)?class 和 for 是 JavaScript 的保留字。
6 this.props.children
????????this.props 對(duì)象的屬性與組件的屬性一一對(duì)應(yīng)然评,但是有一個(gè)例外仅财,就是 this.props.children 屬性。它表示組件的所有子節(jié)點(diǎn)(查看 demo05)沾瓦。
var NotesList = React.createClass({
? render: function() {
??? return (
????????<ol>
????? {
??????? React.Children.map(this.props.children, function (child) {
????????? return <li>{child}</li>;
??? ????})
????? }
????????</ol>
??? );
? }
});
ReactDOM.render(
????<NotesList>
????????<span>hello</span>
????????<span>world</span>
????</NotesList>,
? ? document.body
);
????????上面代碼的 NoteList 組件有兩個(gè) span 子節(jié)點(diǎn)满着,它們都可以通過(guò) this.props.children 讀取谦炒,運(yùn)行結(jié)果如下贯莺。
????????這里需要注意, this.props.children 的值有三種可能:如果當(dāng)前組件沒(méi)有子節(jié)點(diǎn)宁改,它就是 undefined ;如果有一個(gè)子節(jié)點(diǎn)缕探,數(shù)據(jù)類型是 object ;如果有多個(gè)子節(jié)點(diǎn)还蹲,數(shù)據(jù)類型就是 array 爹耗。所以,處理 this.props.children 的時(shí)候要小心谜喊。
????????React 提供一個(gè)工具方法 React.Children來(lái)處理 this.props.children 潭兽。我們可以用 React.Children.map 來(lái)遍歷子節(jié)點(diǎn),而不用擔(dān)心 this.props.children 的數(shù)據(jù)類型是 undefined 還是 object斗遏。更多的 React.Children 的方法山卦,請(qǐng)參考官方文檔。
7 PropTypes
????????組件的屬性可以接受任意值诵次,字符串账蓉、對(duì)象、函數(shù)等等都可以逾一。有時(shí)铸本,我們需要一種機(jī)制,驗(yàn)證別人使用組件時(shí)遵堵,提供的參數(shù)是否符合要求箱玷。
????????組件類的PropTypes屬性怨规,就是用來(lái)驗(yàn)證組件實(shí)例的屬性是否符合要求(查看 demo06)。
var MyTitle = React.createClass({
? propTypes: {
??? title: React.PropTypes.string.isRequired,
? },
? render: function() {
???? return <h1>{this.props.title}</h1>;
?? }
});
????????上面的Mytitle組件有一個(gè)title屬性汪茧。PropTypes 告訴 React椅亚,這個(gè) title 屬性是必須的,而且它的值必須是字符串〔瘴郏現(xiàn)在呀舔,我們?cè)O(shè)置 title 屬性的值是一個(gè)數(shù)值。
var data = 123;
ReactDOM.render(
????<MyTitle title={data} />,
? ? document.body
);
????????這樣一來(lái)扩灯,title屬性就通不過(guò)驗(yàn)證了媚赖。控制臺(tái)會(huì)顯示一行錯(cuò)誤信息珠插。
Warning: Failed propType: Invalid prop `title` of type `number` supplied to `MyTitle`, expected `string`.
????????更多的PropTypes設(shè)置惧磺,可以查看官方文檔。
????????此外捻撑,getDefaultProps 方法可以用來(lái)設(shè)置組件屬性的默認(rèn)值磨隘。
var MyTitle = React.createClass({
? getDefaultProps: function () {
??? return {
????? title: 'Hello World'
??? };
? },
? render: function() {
???? return <h1>{this.props.title}</h1>;
?? }
});
ReactDOM.render(
? <MyTitle />,
? document.body
);
????????上面代碼會(huì)輸出"Hello World"。
8 獲取真實(shí)的DOM節(jié)點(diǎn)
????????組件并不是真實(shí)的 DOM 節(jié)點(diǎn)顾患,而是存在于內(nèi)存之中的一種數(shù)據(jù)結(jié)構(gòu)番捂,叫做虛擬 DOM (virtual DOM)。只有當(dāng)它插入文檔以后江解,才會(huì)變成真實(shí)的 DOM 设预。根據(jù) React 的設(shè)計(jì),所有的 DOM 變動(dòng)犁河,都先在虛擬 DOM 上發(fā)生鳖枕,然后再將實(shí)際發(fā)生變動(dòng)的部分,反映在真實(shí) DOM上桨螺,這種算法叫做 DOM diff宾符,它可以極大提高網(wǎng)頁(yè)的性能表現(xiàn)。
????????但是灭翔,有時(shí)需要從組件獲取真實(shí) DOM 的節(jié)點(diǎn)魏烫,這時(shí)就要用到 ref 屬性(查看 demo07)。
var MyComponent = React.createClass({
? handleClick: function() {
??? this.refs.myTextInput.focus();
??},
? render: function() {
??? return (
????????<div>
? ? ? ? ? ? <input type="text" ref="myTextInput" />
? ? ? ? ? ? <input type="button" value="Focus the text input" onClick={this.handleClick} />
????????</div>
? ? );
? }
});
ReactDOM.render(
????<MyComponent />? ,
? ? document.getElementById('example')
);
????????上面代碼中缠局,組件 MyComponent 的子節(jié)點(diǎn)有一個(gè)文本輸入框则奥,用于獲取用戶的輸入。這時(shí)就必須獲取真實(shí)的 DOM 節(jié)點(diǎn)狭园,虛擬 DOM 是拿不到用戶輸入的读处。為了做到這一點(diǎn),文本輸入框必須有一個(gè) ref 屬性唱矛,然后 this.refs.[refName] 就會(huì)返回這個(gè)真實(shí)的 DOM 節(jié)點(diǎn)罚舱。
????????需要注意的是井辜,由于 this.refs.[refName] 屬性獲取的是真實(shí) DOM ,所以必須等到虛擬 DOM 插入文檔以后管闷,才能使用這個(gè)屬性粥脚,否則會(huì)報(bào)錯(cuò)。上面代碼中包个,通過(guò)為組件指定 Click 事件的回調(diào)函數(shù)刷允,確保了只有等到真實(shí) DOM 發(fā)生 Click 事件之后,才會(huì)讀取 this.refs.[refName] 屬性碧囊。
????????React 組件支持很多事件树灶,除了 Click 事件以外,還有 KeyDown 糯而、Copy天通、Scroll 等,完整的事件清單請(qǐng)查看官方文檔熄驼。
9 this.state
????????組件免不了要與用戶互動(dòng)像寒,React 的一大創(chuàng)新,就是將組件看成是一個(gè)狀態(tài)機(jī)瓜贾,一開(kāi)始有一個(gè)初始狀態(tài)诺祸,然后用戶互動(dòng),導(dǎo)致?tīng)顟B(tài)變化阐虚,從而觸發(fā)重新渲染 UI (查看 demo08)序臂。
var LikeButton = React.createClass({
? getInitialState: function() {
??? return {liked: false};
? },
? handleClick: function(event) {
??? this.setState({liked: !this.state.liked});
? },
? render: function() {
??? var text = this.state.liked ? 'like' : 'haven\'t liked';
??? return (
????????<p onClick={this.handleClick} >
??????? ????You{text} this. Click to toggle.
????????</p>
??? );
? }
});
ReactDOM.render(
????<LikeButton />? ,
? ? document.getElementById('example')
);
????????上面代碼是一個(gè) LikeButton 組件蚌卤,它的 getInitialState 方法用于定義初始狀態(tài)实束,也就是一個(gè)對(duì)象,這個(gè)對(duì)象可以通過(guò) this.state 屬性讀取逊彭。當(dāng)用戶點(diǎn)擊組件咸灿,導(dǎo)致?tīng)顟B(tài)變化,this.setState 方法就修改狀態(tài)值侮叮,每次修改以后避矢,自動(dòng)調(diào)用 this.render 方法,再次渲染組件囊榜。
????????由于 this.props 和 this.state 都用于描述組件的特性审胸,可能會(huì)產(chǎn)生混淆。一個(gè)簡(jiǎn)單的區(qū)分方法是卸勺,this.props 表示那些一旦定義砂沛,就不再改變的特性,而 this.state 是會(huì)隨著用戶互動(dòng)而產(chǎn)生變化的特性曙求。
10 表單
????????用戶在表單填入的內(nèi)容碍庵,屬于用戶跟組件的互動(dòng)映企,所以不能用 this.props 讀取(查看 demo9)静浴。
var Input = React.createClass({
? getInitialState: function() {
??? return {value: 'Hello!'};
? },
? handleChange: function(event) {
??? this.setState({value: event.target.value});
? },
? render: function () {
??? var value = this.state.value;
??? return (
????????<div>
????????????<input type="text" value={value} onChange={this.handleChange} />
????????????<p>{value}</p>
????????</div>
??? );
? }
});
ReactDOM.render(<Input />, document.body);
????????上面代碼中堰氓,文本輸入框的值,不能用 this.props.value 讀取苹享,而要定義一個(gè) onChange 事件的回調(diào)函數(shù)双絮,通過(guò) event.target.value 讀取用戶輸入的值。textarea 元素得问、select元素掷邦、radio元素都屬于這種情況,更多介紹請(qǐng)參考官方文檔椭赋。
11 組件的生命周期
????????組件的生命周期分成三個(gè)狀態(tài):
??? Mounting:已插入真實(shí)DOM
??? Updating:正在被重新渲染
??? Unmounting:已移出真實(shí)DOM
????????React 為每個(gè)狀態(tài)都提供了兩種處理函數(shù)抚岗,will 函數(shù)在進(jìn)入狀態(tài)之前調(diào)用,did 函數(shù)在進(jìn)入狀態(tài)之后調(diào)用哪怔,三種狀態(tài)共計(jì)五種處理函數(shù)宣蔚。
??? componentWillMount()
??? componentDidMount()
??? componentWillUpdate(object nextProps, object nextState)
??? componentDidUpdate(object prevProps, object prevState)
??? componentWillUnmount()
????????此外,React 還提供兩種特殊狀態(tài)的處理函數(shù)认境。
??? componentWillReceiveProps(object nextProps):已加載組件收到新的參數(shù)時(shí)調(diào)用
??? shouldComponentUpdate(object nextProps, object nextState):組件判斷是否重新渲染時(shí)調(diào)用
????????這些方法的詳細(xì)說(shuō)明胚委,可以參考官方文檔。下面是一個(gè)例子(查看 demo10)叉信。
var Hello = React.createClass({
? getInitialState: function () {
??? return {
????? opacity: 1.0
??? };
? },
? componentDidMount: function () {
??? this.timer = setInterval(function () {
????? var opacity = this.state.opacity;
????? opacity-= .05;
????? if (opacity < 0.1) {
??????? opacity= 1.0;
????? }
????? this.setState({
??????? opacity: opacity
????? });
??? }.bind(this), 100);
? },
? render: function () {
??? return (
????????<div style={{opacity: this.state.opacity}}>
??????? ????Hello{this.props.name}
????????</div>
??? );
? }
});
ReactDOM.render(
????<Hello name="world"/>,
? ? document.body
);
????????上面代碼在hello組件加載以后亩冬,通過(guò) componentDidMount 方法設(shè)置一個(gè)定時(shí)器,每隔100毫秒硼身,就重新設(shè)置組件的透明度硅急,從而引發(fā)重新渲染。
????????另外佳遂,組件的style屬性的設(shè)置方式也值得注意营袜,不能寫成
style="opacity:{this.state.opacity};"
????????而要寫成
style={{opacity: this.state.opacity}}
????????這是因?yàn)?React 組件樣式是一個(gè)對(duì)象,所以第一重大括號(hào)表示這是 JavaScript 語(yǔ)法丑罪,第二重大括號(hào)表示樣式對(duì)象荚板。
12 Ajax
????????組件的數(shù)據(jù)來(lái)源,通常是通過(guò) Ajax 請(qǐng)求從服務(wù)器獲取吩屹,可以使用 componentDidMount 方法設(shè)置 Ajax 請(qǐng)求跪另,等到請(qǐng)求成功,再用 this.setState 方法重新渲染 UI (查看 demo11)煤搜。
var UserGist = React.createClass({
? getInitialState: function() {
??? return {
????? username: '',
????? lastGistUrl: ''
??? };
? },
? componentDidMount: function() {
??? $.get(this.props.source, function(result) {
????? var lastGist = result[0];
????? if (this.isMounted()) {
??????? this.setState({
????????? username: lastGist.owner.login,
????????? lastGistUrl: lastGist.html_url
??????? });
????? }
??? }.bind(this));
? },
? render: function() {
??? return (
????????<div>
??????? ????{this.state.username}'s last gist is
? ? ? ? ? ? <a href={this.state.lastGistUrl}>?here.</a>
????????</div>
??? );
? }
});
ReactDOM.render(
????<UserGist source="https://api.github.com/users/octocat/gists" />,
? ? document.body
);
????????上面代碼使用jQuery 完成 Ajax 請(qǐng)求免绿,這是為了便于說(shuō)明。React 本身沒(méi)有任何依賴宅楞,完全可以不用jQuery针姿,而使用其他庫(kù)袱吆。
????????我們甚至可以把一個(gè)Promise對(duì)象傳入組件,請(qǐng)看Demo12距淫。
ReactDOM.render(
????<RepoList promise={$.getJSON('https://api.github.com/search/repositories?q=javascript&sort=stars')}/>,
? ????document.body
);
????????上面代碼從Github的API抓取數(shù)據(jù)绞绒,然后將Promise對(duì)象作為屬性,傳給RepoList組件榕暇。
????????如果Promise對(duì)象正在抓取數(shù)據(jù)(pending狀態(tài))蓬衡,組件顯示"正在加載";如果Promise對(duì)象報(bào)錯(cuò)(rejected狀態(tài))彤枢,組件顯示報(bào)錯(cuò)信息狰晚;如果Promise對(duì)象抓取數(shù)據(jù)成功(fulfilled狀態(tài)),組件顯示獲取的數(shù)據(jù)缴啡。
var RepoList = React.createClass({
? getInitialState: function() {
??? return { loading: true, error: null, data: null};
? },
? componentDidMount() {
??? this.props.promise.then(
????? value=> this.setState({loading: false, data: value}),
????? error=> this.setState({loading: false, error: error}));
? },
? render: function() {
??? if (this.state.loading) {
????? return <span>Loading...</span>;
??? }
??? else if (this.state.error !== null) {
????? return <span>Error: {this.state.error.message}</span>;
??? }
??? else {
????? var repos = this.state.data.items;
????? var repoList = repos.map(function (repo) {
??????? return (
????????????<li>
????????????????<a href={repo.html_url}>{repo.name}</a>?
????????????????({repo.stargazers_count} stars) <br/>{repo.description}
????????????</li>
??????? );
????? });
????? return (
????????<main>
????????????<h1>Most Popular JavaScript Projects in Github</h1>
????????????<ol>{repoList}</ol>
????????</main>
????? );
??? }
? }
});
13 參考鏈接
???????? React's official site
???????? React's official examples
???????? React (Virtual) DOM Terminology, by Sebastian Markb?ge
???????? The React Quick Start Guide, by Jack Callister
???????? Learning React.js: Getting Started?and Concepts, by KenWheeler
???????? Getting started with React, by Ryan Clark
???????? React JS Tutorial and Guide to the?Gotchas, by JustinDeal
???????? React Primer, by Binary Muse
???????? jQuery versus React.js thinking, by zigomir
React入門實(shí)例教程