前端學習對於咱們來講愈來愈不友好,特別是隨着這幾年的發展,入門門檻愈來愈高,連進階道路都變成了一場馬拉松。在學習過程當中,咱們面臨不少選擇,vue與react即是一個兩難的選擇。css
二者都是很是優秀的框架,並且我不能很是主觀的說誰好誰很差。可是從咱們初學者的角度來講,其實咱們沒有必要去考慮誰的性能更好,誰的實現更優雅,誰更適合什麼場景等各類因素,惟一一個須要考慮的標準就是,學習誰,可以讓咱們更快的掌握它。由於不管他們兩個你掌握了誰,都可以讓你在找工做時更有底氣。這就足夠了。html
所以,我這篇文章的目的,則是但願從與官方文檔不一樣的角度,來試圖讓react學習變得更加容易,若是你想要學習react,不妨花點時間讀下去。前端
對於vue的學習,不少朋友有一個大的誤解,認爲vue官方出了中文文檔,因此掌握起來會更加容易。然而事實上並不是如此。vue
官方文檔可能告訴了你vue/react的基礎知識有哪些,但是這些知識怎麼用,官方文檔並無告訴咱們。並且vue官方文檔爲了下降學習門檻(繞開了vue-cli),在講述知識的時候,很多地方其實與實際開發是有差距的,這個差距會致使你看完了官方文檔,仍然不知道如何使用vue作一些事情。node
固然,這樣的問題,react官方文檔也存在。雖然對於經驗豐富的大神來講,這並非問題,可是對於新人來講,這樣的差距每每會使得你們有一種似懂非懂的感受。react
這也是我爲何要從和官方文檔不同的角度來入手的緣由。jquery
react中文文檔:http://www.react-cn.com/docs/getting-started.htmlios
在準備學習本文的react知識以前,但願你已經擁有了ES6的知識與知道了create-react-app
的安裝與使用,咱們的學習將會創建在這基礎之上,若是你暫時尚未接觸過他們,不用擔憂,能夠回過頭去閱讀個人前兩篇文章。不用花太多時間就能夠初步掌握。git
ES6經常使用知識合集
詳解create-react-app 與 ES6 modulesgithub
你能夠暫時不用對react有什麼基礎的瞭解,咱們能夠從0開始,固然,若是你看過官方文檔或者從其餘地方學習過相關知識就更好了。
首先,假設你已經在電腦上安裝好了create-react-app
並知道如何使用,那麼我就開始在你電腦上存放開發項目的目錄(本文中假設爲develop)裏開始建立一個名爲first-react
的react項目。操做順序以下:
1
2
3
4
5
6
7
8
9
10
11
|
// 在develop目錄建立first-react項目
> create-react-app first-react
// 進入新建立的項目
> cd first-react
// 安裝項目依賴包
> npm install
// 安裝完畢以後啓動項目
> npm start
|
啓動以後,效果分別以下圖所示:
自動生成的項目是一個簡單的react demo。這個時候項目中會有三個文件夾,咱們來分別瞭解一下這三個文件夾的做用。
咱們在最初學習開發一個頁面的時候,就已經知道一個頁面會有一個html文件,好比index.html,而後分別在html文件中,經過script與link標籤引入js與css。可是在構建工具中,咱們只須要按照必定的規則來組織文件便可,整合的工做構建工具會自動幫助咱們完成,這也是構建工具給前端開發帶來的便利之處,也由於如此,前端的模塊化開發才成爲了可能。
咱們仍是和上一篇文章中說的同樣,先清空src目錄裏全部的其餘文件,僅僅只留下空的入口文件index.js
,並在index.js
寫入以下的代碼:
1
2
3
4
5
6
7
|
// src/index.js
import React from 'react';
import { render } from 'react-dom';
const root = document.querySelector('#root');
render(<div>Hello World!</div>, root);
|
保存以後,結果以下:
如何你能輕鬆看懂這四行代碼,那麼說明你離掌握react已經不遠了。至少你已經掌握了ES6的相關知識。我來解釋一下這些代碼的做用。
import React from 'react';
npm install
指令安裝依賴包的時候,就已經安裝好了react,所以咱們能夠直接import。這句話的做用就在於,可以讓構建工具在當前模塊中識別jsx。而jsx,是一種相似於html標籤的模板語言,咱們只須要懂得html標籤,就沒必要花費額外的精力去了解jsx,由於咱們能夠直接理解爲它就是html標籤,可是在此基礎上,擴展了更多的能力。例如這裏,程序可以識別
,正是這句話的做用。
import { render } from 'react-dom';
react-dom
的render
方法。render方法的做用,就是將react組件,渲染進DOM結構中,它的第一個參數就是react 組件,第二個參數則是一個DOM元素對象。const root = document.querySelector('#root');
render(<div>Hello World!</div>, root);
render
方法,將寫好的react組件渲染進DOM元素對象。而這裏的
root
,則是在
index.html
中寫好的一個元素。這裏的
div
,能夠理解爲一個最簡單的react組件。
OK,理解了這些,那麼咱們就能夠開始學習react最核心的內容組件
了。
曾經,建立react組件有三種方式,可是既然都決定在ES6的基礎上來學習react了,那麼我也就只介紹其中的兩種方式了。反正另一種方式也已經被官方廢棄。
當一個組件,並無額外的邏輯處理,僅僅只是用於數據的展現時,咱們推薦使用函數式的方式來建立一個無狀態組件。
咱們結合簡單的例子來理解。在項目的src目錄裏建立一個叫作helloWorld.jsx
的文件。在該文件中,咱們將建立一個正式的react組件,代碼以下:
1
2
3
4
5
6
7
8
9
10
|
// src/helloWorld.jsx
import React from 'react';
const HelloWorld = () => {
return (
<div>Hello World!</div>
)
}
export default HelloWorld;
|
並在index.js
中引入該組件。修改index.js
代碼以下:
1
2
3
4
5
6
7
8
9
10
|
// src/index.js
import React from 'react';
import { render } from 'react-dom';
// 引入HelloWorld組件
import HelloWorld from './helloWorld';
const root = document.querySelector('#root');
render(<HelloWorld />, root);
|
保存後運行,咱們發現結果同樣。
在helloWorld.jsx
中,咱們仍然引入了react
,是由於全部會涉及到jsx模板的組件,咱們都要引入它,這樣構建工具纔會識別獲得。
組件裏只是簡單的建立了一個HelloWorld函數,並返回了一段html(jsx模板)。並在最後將HelloWorld函數做爲對外的接口暴露出來export default HelloWorld
。
接下來咱們經過一點一點擴展HelloWorld組件能力的方式,來學習組件相關的基礎知識。
向組件內部傳遞參數
向組件內部傳遞參數的方式很簡單,這就和在html標籤上添加一個屬性同樣。
例如咱們但願向HelloWorld組件內傳遞一個name屬性。那麼只須要咱們在使用該組件的時候,添加一個屬性便可。
1
2
|
<HelloWorld name="Tom" />
<HelloWorld name="Jake" />
|
若是咱們但願組件最終渲染的結果是諸如:Tom say: Hello, world!
其中的名字能夠在傳入時自定義。那麼咱們在組件中應該如何接收傳遞進來的參數呢?
咱們修改HelloWorld.jsx
以下:
1
2
3
4
5
6
7
8
9
10
11
|
// src/helloWorld.jsx
import React from 'react';
const HelloWorld = (props) => {
console.log(props);
return (
<div>{ props.name } say: Hello World!</div>
)
}
export default HelloWorld;
|
並在index.js中修改render方法的使用,向組件中傳入一個name屬性
1
2
|
// src/index.js
render(, root);
|
結果以下:
在HelloWorld
組件中,我使用了一個叫作props的參數。而經過打印出來props能夠得知,props正是一個組件在使用時,全部傳遞進來屬性組合而成的一個對象。你們也能夠在學習時多傳入幾個額外的參數,他們都會出如今props對象裏。
而在jsx模板中,經過
這樣的方式來將變量傳入進來。這是jsx模板語言支持的一種語法,你們記住能用便可。
你們要記住,使用這種方式建立的無狀態組件,會比另一種方式性能更高,所以若是你的組件僅僅只是用於簡單的數據展現,沒有額外的邏輯處理,就要優先選擇這種方式。
那麼咱們繼續升級HelloWorld組件的能力。如今我但願有一個點擊事件,當咱們點擊該組件時,會在Console工具中打印出傳入的name值。這就涉及到了另一種組件的建立,也就是當咱們的組件開始有邏輯處理,以前的那種方式勝任不了時索要採起的一種形式。
修改helloWorld.jsx
文件以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// helloWorld.jsx
import React, { Component } from 'react';
class HelloWorld extends Component {
clickHander = () => {
console.log(this.props);
console.log(this.props.name);
}
render () {
return (
<div onClick={this.clickHander}>{ this.props.name } say: Hello World!</div>
)
}
}
export default HelloWorld;
|
若是,你同時熟知第一種react組件的建立方式,與ES6語法的話,相信上面的代碼,並不會對你形成多少困擾。
沒錯,這種方式建立的組件,正是經過繼承react的Component
對象而來。因此建立的方式也是利用ES6的class語法來生成。也正由於如此,其中的不少實用方式,也就跟class的使用同樣了。
上面的render方法,則是Component中,專門提供的用來處理jsx模板的方法。
與第一種方式不一樣的是,咱們接收傳入進來的參數,使用的是this.props
,第一種方式將props放置於函數參數中,而這種方式則是將props掛載與實例對象上,所以會有所不一樣。
而咱們想要給一個組件添加點擊事件,方式也與html標籤中幾乎一致
react事件相關的知識你們能夠當作一個進階課程去研究,這裏就暫時很少說,詳情能夠參考官方文檔 https://facebook.github.io/react/docs/events.html
好了,如今你們初步認識了react的第二種組件的建立方式,那麼咱們繼續搞事情,如今我想要的效果,是傳入兩個名字,name1=Tom, name2='Jason'
,我但願第一次點擊時,log出Tom,第二次log出Jason,第三次Tom…
這個時候,咱們就須要引入react組件很是核心的知識狀態state
。
修改helloWorld.jsx
代碼以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
// helloWorld.jsx
import React, { Component } from 'react';
class HelloWorld extends Component {
state = {
switch: 0,
name: this.props.name1
}
clickHander = () => {
const { name1, name2 } = this.props;
if (this.state.switch === 0) {
console.log(name1);
this.setState({
switch: 1,
name: name2
})
} else {
console.log(name2);
this.setState({
switch: 0,
name: name1
})
}
}
render () {
return (
<div onClick={this.clickHander}>{ this.state.name } say: Hello World!</div>
)
}
}
export default HelloWorld;
|
先來講說state相關的基礎知識。首先了解ES6 class語法的同窗都應該知道,當咱們經過這種方式來寫的時候,實際上是將state寫入了構造函數之中。
1
2
3
4
|
state = {}
// 等同於ES5構造函數中的
this.state = {}
|
所以深刻掌握class語法對於學習react組件的幫助很是巨大,咱們須要清楚的知道什麼樣的寫法會放入對象的什麼位置,是構造函數中,仍是原型中等。這也是爲何開篇我會強調必定要先對個人前兩篇文章所介紹的知識有必定了解才行。
所以,在對象中,咱們能夠經過this.state
的方式來訪問state中所存儲的屬性。同時,react還提供了以下的方式來修改state的值
1
2
3
|
this.setState({
name: 'newName'
})
|
setState
接收一個對象,它的運行結果相似於執行一次assign方法。會修改傳入的屬性,而其餘的屬性則保持不變。
react賦予state的特性,則是當state被修改時,會引發組件的一次從新渲染。即render方法會從新執行一次。也正是因爲這個特性,所以當咱們想要改變界面上的元素內容時,經常只須要改變state中的值就好了。這也是爲何結合render方法,咱們能夠再也不須要jquery的緣由所在。
而setState
也有一個很是重要的特性,那就是,該方法是異步的。它並不會當即執行,而會在下一輪事件循環中執行。
說到這裏,基礎薄弱的同窗就開始頭暈了,這就是爲何我在前面的文章都反覆強調基礎知識的重要性,基礎紮實,不少東西稍微一提,你就知道是怎麼回事,不紮實,處處都是讓你頭暈的點,不知道的不要緊,讀我這篇文章 http://www.jianshu.com/p/12b9f73c5a4f。
相信不理解這個點的同窗確定會遇到不少坑,因此千萬要記住了。
1
2
3
4
5
6
7
|
// 假設state.name的初始值爲Tom,咱們改變它的值
this.setState({
name: 'Jason'
})
// 而後當即查看它的值
console.log(this.state.name) // 仍然爲Tom,不會當即改變
|
咱們知道,react組件實際上是虛擬DOM,所以一般咱們須要經過特殊的方式才能拿到真正的DOM元素。大概說一說虛擬DOM是個什麼形式存在的,它其實就是經過js對象的方式將DOM元素相關的都存儲其實,好比一個div元素可能會是這樣:
1
2
3
4
5
6
7
8
9
|
// 固然可能命名會是其餘的,大概表達一個意思,不深究哈
{
nodeName: 'div',
className: 'hello-world',
style: {},
parentNodes: 'root',
childrenNodes: []
...
}
|
而咱們想要拿到真實的DOM元素,react中提供了一種叫作ref
的屬性來實現這個目的。
修改helloWorld.jsx
以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import React, { Component } from 'react';
class HelloWorld extends Component {
clickHander = () => {
console.log(this.refs)
}
render () {
return (
<div className="container" onClick={this.clickHander}>
<div ref="hello" className="hello">Hello</div>
<div ref="world" className="world">World</div>
</div>
)
}
}
export default HelloWorld;
|
爲了區分ES6語法中的class關鍵字,當咱們在jsx中給元素添加class時,須要使用
className
來代替
咱們在jsx中,能夠給元素添加ref
屬性,而這些擁有ref屬性的元素,會統一放在組件對象的refs
中,所以,當咱們想要訪問對應的真實DOM時,則經過this.refs
來訪問便可。
固然,ref的值不只僅能夠爲一個名字,同時還能夠爲一個回調函數,這個函數會在render渲染時執行,也就是說,每當render函數執行一次,ref的回調函數也會執行一次。
修改helloWorld.jsx
以下,感覺一下ref回調的知識點
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// src/helloWorld.jsx
import React, { Component } from 'react';
class HelloWorld extends Component {
clickHander = () => {
console.log(this.refs)
}
refCallback = (elem) => {
console.log(elem);
}
render () {
return (
<div className="container" onClick={this.clickHander}>
<div ref="hello" className="hello">Hello</div>
<div ref={this.refCallback} className="world">World</div>
</div>
)
}
}
export default HelloWorld;
|
大概介紹一下我暫時能想到的ref使用的一個場景。例如咱們要實現元素拖拽的時候,或者寫一個slider組件。咱們可能會很是頻繁的改動元素的位置。這個時候,若是咱們仍然經過react組件的state來存儲元素的位置,那麼就會致使react組件過於頻繁的渲染,這就會引起一個嚴重的性能問題。因此這個時候咱們不得不獲取到真實DOM,並經過常規的方式來作。
一樣的道理也適用於vue中,咱們要儘可能避免將可能會變更頻率很是高的屬性存放於vue組件的data中。
所謂組件的生命週期,指的就是一個組件,從建立到銷燬的這樣一個過程。
而react爲組件的生命週期提供了不少的鉤子函數。不少地方也爲生命週期畫了很清晰明瞭的圖幫助你們理解。可是我在初學的時候其實並無看懂,仍是在我懂得了生命週期以後,纔看懂的那些圖。因此呢,這裏我也就不去找圖了。咱們這樣理解。
通俗來講,react爲一個組件,劃分了以下的時刻。
componentWillMount
渲染完成以前componentDidMount
渲染完成以後所謂的渲染完成,即組件已經被渲染成爲真實DOM並插入到了html之中。
componentWillReceiveProps
接收到一個新的props時,在從新render以前調用shouldComponentUpdate
接收到一個新的state或者props時,在從新render以前調用componentWillUpdate
接收到一個新的state或者props時,在從新render以前調用componentDidUpdate
組件完成更新以後調用componentWillUnmount
在學習之初你不用記住這些函數的具體名字,你只須要記住這三個大的時刻便可,第一次渲染完成先後,更新先後,取消以前。當你要使用時,再查具體對應的名字叫什麼便可。
並且根據個人經驗,初學之時,其實也不知道這些鉤子函數會有什麼用,會在何時用,這須要咱們在實踐中慢慢掌握,因此也不用着急。當咱們上手寫了幾個稍微複雜的例子,天然會知道如何去使用他們。
因此這裏我只詳細介紹一下,咱們最經常使用的一個生命週期構造函數,組件第一次渲染完成以後調用的componentDidMount
。
既然是組件第一次渲染完成以後纔會調用,也就是說,該函數在react組件的生命週期中,只會調用一次。而渲染完成,則表示組件已經被渲染成爲真實DOM插入了html中。因此這時候就能夠經過ref獲取真實元素。記住它的特色,這會幫助咱們正確的使用它。
修改helloWorld.jsx
以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// src/helloWorld.jsx
import React, { Component } from 'react';
class HelloWorld extends Component {
clickHander = () => {
console.log(this.refs)
}
// 這時已經能夠獲取到真實DOM,而componentWillMount則不行
componentDidMount (props) {
console.log(this.refs)
}
render () {
return (
<div className="container" onClick={this.clickHander}>
<div ref="hello" className="hello">Hello</div>
<div ref="world" className="world">World</div>
</div>
)
}
}
export default HelloWorld;
|
咱們在實際開發中,經常須要經過ajax獲取數據,而數據請求的這個行爲,則最適合放在componentDidMount
中來執行。
一般會在首次渲染改變組件狀態(state)的行爲,或者稱之爲有反作用的行爲,都建議放在
componentDidMount
中來執行。主要是由於state的改動會引起組件的從新渲染。
做爲react學習中的一個很是重要的點,組件之間的交互仍是須要咱們認真掌握的。這個時候hello world就知足不了咱們學習的慾望了,因此咱們能夠先把它給刪掉。
那麼組件之間的交互,大概能夠分爲以下兩種:
固然可能有的人會問,2個不相干的組件之間如何交互?若是,你的代碼裏,出現了兩個不相干的組件還要交互,那說明你的組件劃分確定是有問題的。這就是典型的給本身挖坑找事兒。即便確實有,那也是經過react-redux把他們變成子組件對吧。可是,一般狀況下,不到萬不得已,並不建議使用react-redux,除非你的項目確實很是龐大了,須要管理的狀態很是多了,已經不得不使用,必定要記住,react-redux這類狀態管理器是最後的選擇。
咱們來想一想一個簡單常見的場景:頁面裏有一個submit提交按鈕,當咱們點擊提交後,按鈕前出現一個loading圖,並變爲不可點擊狀態,片刻以後,接口請求成功,飄出一個彈窗,告訴你,提交成功。你們能夠想想,這種場景,藉助react組件應該如何作?
首先能夠很簡單的想到,將按鈕與彈窗分別劃分爲兩個不一樣的組件:
。而後建立一個父組件來管理這兩個子組件
。
那麼在父組件中,咱們須要考慮什麼因素?Button的loading圖是否展現,彈窗是否展現對吧。
OK,根據這些思考,咱們開始來實現這個簡單的場景。
首先建立一個Button
組件。在src目錄下建立一個叫作Button.jsx
的文件,代碼以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// src/Button.jsx
import React from 'react';
const Button = (props) => {
const { children, loading, submit } = props
return (
<button onClick={submit} disabled={ loading ? 'disabled' : null }>
{ loading && <i className="loading"></i> }
{ children }
</button>
)
}
export default Button;
|
注意,當你引入了一個新建立的文件時,可能須要從新啓動服務纔會找獲得新的組件
因爲這裏的Button組件僅僅是簡單的展現,並沒有額外的邏輯須要處理,所以咱們使用無狀態的組件。在這個組件裏,出現了一個新的知識點:children
1
2
3
4
5
6
7
8
9
|
// 假如咱們這樣使用Button組件時
<Button>確認</Button>
// 那麼標籤中間的確認二字就會放入props的children屬性中
// 無狀態組件中
props.children = '確認'
// 有狀態組件中
this.props.children = '確認'
|
固然,children還能夠是更多的元素,這和咱們熟知的DOM元素的children保持一致。
還有一個須要注意的知識點,則是在jsx模板中,咱們可使用JavaScript表達式來執行簡單的邏輯處理
咱們能夠列舉一些常見的表達式:
1
2
3
4
5
6
7
|
<div>{ message }</div>
<Button disabled={ loading ? 'disabled' : null }></Button>
{ dialog && <Dialog /> }
{ pending ? <Aaaa /> : <Bbbb /> }
|
若是對於JavaScript表達式瞭解不夠多的朋友,建議深刻學習一下相關的知識。
理解了這些知識以後,相信對於上面的Button組件所涉及到的東西也就可以很是清楚知道是怎麼回事了。接下來,咱們須要建立一個彈窗組件,Dialog.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// src/Dialog.jsx
import React, { Component } from 'react';
const Dialog = (props) => {
const { message, close } = props;
return (
<div className="dialog-backdrop">
<div className="dialog-container">
<div className="dialog-header">提示</div>
<div className="dialog-body">{ message }</div>
<div className="dialog-footer">
<button className="btn" onClick={ close }>肯定</button>
</div>
</div>
</div>
)
}
export default Dialog;
|
這個組件沒有太多特別的東西,惟一須要關注的一點是,咱們也能夠經過props傳遞一個函數給子組件。例如這裏的close方法。該方法在父組件中定義,可是卻在子組件Dialog中執行,他的做用是關閉彈窗。
咱們很容易知道父組件想要修改子組件,只須要經過改變傳入的props屬性便可。那麼子組件想要修改父組件的狀態呢?正是父組件經過向子組件傳遞一個函數的方式來改變。
該函數在父組件中定義,在子組件中執行。而函數的執行內容,則是修改父組件的狀態。這就是close的原理,咱們來看看父組件中是如何處理這些邏輯的。
建立一個父組件App.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
// src/App.jsx
import React, { Component } from 'react';
import Button from './Button';
import Dialog from './Dialog';
import './style.css';
class App extends Component {
state = {
loading: false,
dialog: false,
message: 'xxx'
}
submit = () => {
this.setState({
loading: true
})
// 模擬數據請求的過程,假設數據請求會經歷1s獲得結果
setTimeout(() => {
// 經過隨機數的方式模擬可能出現的成功與失敗兩種結果
const res = Math.random(1);
if (res > 0.5) {
this.setState({
dialog: true,
message: '提交成功!'
})
} else {
this.setState({
dialog: true,
message: '提交失敗!'
})
}
this.setState({ loading: false })
}, 1000)
}
close = () => {
this.setState({
dialog: false
})
}
render () {
const { loading, dialog, message } = this.state;
return (
<div className="app-wrap">
<Button loading={ loading } submit={ this.submit }>提交</Button>
{ dialog && <Dialog message={ message } close={ this.close } /> }
</div>
)
}
}
export default App;
|
App組件的state中,loading用於判斷Button按鈕是否顯示loading圖標,dialog用於判斷是否須要顯示彈窗,message則是表示彈窗的提示內容。
咱們自定義的鉤子函數submit
和close
則分別是與子組件Button與Dialog交互的一個橋樑。前面咱們說過了,想要在子組件中改變父級的狀態,就須要經過在父組件中建立鉤子函數,並傳遞給子組件執行的方式來完成。
在App.jsx中咱們還看到代碼中引入了一個css文件。這是構建工具幫助咱們整合的方式,咱們能夠直接將css文件當作一個單獨的模塊引入進來。咱們還能夠經過一樣的方式引入圖片等資源。
style.css也是在src目錄下建立的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
// src/style.scss
button {
background: none;
border: none;
outline: none;
width: 100px;
height: 30px;
border: 1px solid orange;
border-radius: 4px;
font-size: 16px;
display: block;
margin: 20px auto;
}
.loading {
display: inline-block;
width: 10px;
height: 10px;
border: 2px solid #ccc;
border-radius: 10px;
margin-right: 10px;
border-bottom: transparent;
border-top: transparent;
animation-name: loading;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
.dialog-backdrop {
background: rgba(0, 0, 0, 0.2);
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.dialog-container {
width: 300px;
background: #FFFFFF;
border-radius: 4px;
position: absolute;
top: 20%;
left: 50%;
transform: translate(-50%, -50%);
padding: 10px;
}
.dialog-header {
height: 20px;
text-align: center;
line-height: 20px;
}
.dialog-body {
line-height: 1.6;
text-align: center;
margin-top: 20px;
}
.dialog-footer {
margin-top: 20px;
}
.dialog-footer button {
margin: 0 auto;
border: none;
background: orange;
color: #fff;
}
@keyframes loading {
from {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
}
|
最後修改index.js,便可將程序運行起來。
1
2
3
4
5
6
7
8
|
// src/index.js
import React from 'react';
import { render } from 'react-dom';
import App from './App';
const root = document.querySelector('#root');
render(, root);
|
那麼總結一下組件之間的交互。
父組件改變子組件,經過改變傳入的props屬性值便可。
而子組件改變父組件的狀態,則須要在父組件中建立鉤子函數,而後讓鉤子函數經過props傳遞給子組件,並在子組件中執行。
那麼子組件與子組件之間的交互方式,也就是經過影響共同的父組件來進行交互的。正如咱們這個例子中的點擊按鈕,出現彈窗同樣。這就是react組件之間交互的核心。
在學習異步組件以前,可能還須要你們去折騰一下如何禁用瀏覽器的跨域限制。禁用跨域限制可讓咱們使用更多的公共api進行學習,可是不少人並不知道還能夠這樣玩。總之一句話,知道了如何禁用瀏覽器的跨域限制,會讓你的學習速度提高不少,不少項目你就能夠動手本身嘗試了。
我這裏只能提供在mac環境下如何禁用chrome瀏覽器的跨域限制。在命令行工具中輸入如下指令啓動chrome便可。
1
|
> open -a "Google Chrome" --args --disable-web-security --user-data-dir
|
在safari瀏覽器中則更加簡單。
windows環境下如何作須要你們本身去研究。
OK,禁用跨域限制之後,咱們就能夠自如的請求別人的接口。這個時候再來學習異步組件就能輕鬆不少。
異步組件並非那麼複雜,因爲接口請求會經歷一點時間,所以在組件第一次渲染的時候,並不能直接將咱們想要的數據渲染完成,那麼就得再接口請求成功以後,從新渲染一次組件。上面的知識已經告訴你們,經過使用this.setState
修改state的值能夠達到從新渲染的目的。
因此咱們一般的作法就是在接口請求成功以後,使用this.setState
。
爲了下降學習難度,咱們暫時先使用jquery中提供的方法來請求數據。
目前比較經常使用的是axios
首先在咱們的項目中,安裝jquery庫。咱們一般都會使用這樣的方式來安裝新的組件和庫。
1
|
> npm install jquery
|
而後在src目錄下建立一個News.jsx,藉助知乎日報的api,咱們來嘗試完成一個簡單的異步組件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
// src/News.jsx
import React, { Component } from 'react';
import $ from 'jquery';
class News extends Component {
state = {
stories: [],
topStories: []
}
componentDidMount() {
$.get('http://news-at.zhihu.com/api/4/news/latest').then(resp => {
console.log(resp);
this.setState({
stories: resp.stories,
topStories: resp.top_stories
})
})
}
render() {
const { stories, topStories } = this.state;
// 觀察每一次render數據的變化
console.log(this.state);
return (
<div className="latest-news">
<section className="part1">
<div className="title">最熱</div>
<div className="container">
{
topStories.map((item, i) => (
<div className="item-box" key={i}>
<img src={ item.image } alt=""/>
<div className="sub-title">{ item.title }</div>
</div>
))
}
</div>
</section>
<section className="part2">
<div className="title">熱門</div>
<div className="container">
{
stories.map((item, i) => (
<div className="item-box" key={i}>
<img src={ item.images[0] } alt=""/>
<div className="sub-title">{ item.title }</div>
</div>
))
}
</div>
</section>
</div>
)
}
}
export default News;
|
在style.css
中簡單補上相關的css樣式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
// src/style.css
.latest-news {
width: 780px;
margin: 20px auto;
}
.latest-news section {
margin-bottom: 20px;
}
.latest-news .title {
height: 40px;
line-height: 40px;
font-size: 16px;
padding: 0 10px;
}
.latest-news .container {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
.latest-news .item-box {
width: 30%;
overflow: hidden;
margin-bottom: 20px;
}
.latest-news .item-box img {
width: 100%;
height: 200px;
}
.latest-news .item-box .sub-title {
font-size: 12px;
line-height: 1.6;
margin-top: 10px;
}
|
並在App.jsx中引入使用便可。
1
2
3
4
5
|
// src/App.jsx
import News from './News';
// 將下面這一句放於render函數的jsx模板中便可
|
這個組件除了獲取數據,沒有額外的邏輯處理,但仍然有幾個須要很是注意的地方。
一、 若非特殊狀況,儘可能保證數據請求的操做在componentDidMount
中完成。
二、 react中的列表渲染一般經過調用數組的原生方法map方法來完成,具體使用方式可參考上例。
三、爲了確保性能,被渲染的每一列都須要給他配置一個惟一的標識,正入上慄中的key={i}
。咱們來假想一個場景,若是咱們在數組的最前面新增一條數據,若是沒有惟一的標識,那麼全部的數據都會被從新渲染,一旦數據量過大,這會形成嚴重的性能消耗。惟一標識會告訴react,這些數據已經存在了,你只須要渲染新增的那一條就能夠了。
四、若是你想要深刻了解該組件的具體變化,你能夠在render方法中,經過console.log(this.state)
的方式,觀察在整個過程當中,組件渲染了多少次,已經每一次this.state
中的具體值是什麼,是如何變化的。
不少人寫文章喜歡把問題複雜化,所以當我學習高階組件的時候,查閱到的不少文章都給人一種高階組件高深莫測的感受。可是事實上卻未必。咱們經常有一些口頭俗語,好比說「包一層」就是能夠用來簡單解釋高階組件的。在普通組件外面包一層邏輯,就是高階組件。
在進一步學習高階組件以前,咱們來回顧一下new與構造函數之間的關係。在前面我有文章提到過爲何構造函數中this在運行時會指向new出來的實例,不知道還有沒有人記得。我將那段代碼複製過來。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
// 先一本正經的建立一個構造函數,其實該函數與普通函數並沒有區別
var Person = function(name, age) {
this.name = name;
this.age = age;
this.getName = function() {
return this.name;
}
}
// 將構造函數以參數形式傳入
function New(func) {
// 聲明一箇中間對象,該對象爲最終返回的實例
var res = {};
if (func.prototype !== null) {
// 將實例的原型指向構造函數的原型
res.__proto__ = func.prototype;
}
// ret爲構造函數執行的結果,這裏經過apply,將構造函數內部的this指向修改成指向res,即爲實例對象
var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
// 當咱們在構造函數中明確指定了返回對象時,那麼new的執行結果就是該返回對象
if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
return ret;
}
// 若是沒有明確指定返回對象,則默認返回res,這個res就是實例對象
return res;
}
// 經過new聲明建立實例,這裏的p1,實際接收的正是new中返回的res
var p1 = New(Person, 'tom', 20);
console.log(p1.getName());
// 固然,這裏也能夠判斷出實例的類型了
console.log(p1 instanceof Person); // true
|
在上面的例子中,首先咱們定義了一個本質上與普通函數沒區別的構造函數,而後將該構造函數做爲參數傳入New函數中。我在New函數中進行了一些的邏輯處理,讓New函數的返回值爲一個實例,正由於New的內部邏輯,讓構造函數中的this可以指向返回的實例。這個例子就是一個「包一層」的案例。
再來看一個簡單的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import React, { Component } from 'react';
class Div extends Component {
componentDidMount() {
console.log('這是新增的能力');
}
render () {
return (
<div>{ this.props.children }</div>
)
}
}
export default Div;
|
在上面的例子中,咱們把html的DIV標籤做爲基礎元件。對他新增了一個輸出一條提示信息的能力。而新的Div組件,就能夠理解爲div標籤的高階組件。因此到這裏但願你們已經理解了包一層的具體含義。
react組件的高階組件,就是在基礎react組件外面包一層,給該基礎組件賦予新的能力。
OK,咱們來試試定義第一個高階組件,該高階組件的第一個能力,就是向基礎組件中傳入一個props參數。
在例子中,傳入的參數可能沒有任何實際意義,可是在實際開發中,咱們能夠傳入很是有必要的參數來簡化咱們的代碼和邏輯。
先來定義一個擁有上述能力的高階組件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// src/Addsss.jsx
import React from 'react';
// 定義一個接受一個react組件做爲參數的函數
function Addsss(Container) {
// 該函數返回一個新的組件,咱們能夠在該組件中進行新能力的附加
return class Asss extends React.Component {
componentDidMount() {}
render() {
return (
{ this.props.children }
)
}
}
}
export default Addsss;
|
儘管這個高階組價足夠簡單,可是他已經呈現了高階組件的定義方式。如今咱們在一個基礎組件中來使用該高階組件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// src/basic.jsx
import React, { Component } from 'react';
import Addsss from './Addsss';
class Basic extends Component {
componentDidMount() {
console.log(this.props.name);
}
render() {
return (
<div className={this.props.name}>{ this.props.children }</div>
)
}
}
export default Addsss(Basic);
|
咱們看到其實在基礎組件中,對外拋出的接口是Addsss(Basic)
,這是高階組件裏定義的函數運行的結果。也就是說,其實基礎組件中返回的是高階組件中定義的Asss中間組件。這和new的思路幾乎徹底一致。
固然,想要理解,並熟練使用高階組件並非一件容易的事情,你們初學時也不用非要徹底掌握他。當你對react慢慢熟練以後,你能夠嘗試使用高階組件讓本身的代碼更加靈活與簡練。這正是向函數式編程思惟轉變的一個過程。
在進步學習的過程當中,你會發現不管是路由組件react-router,或者react-redux都會使用高階組件來實現一些功能。只要你遇到他們的時候,你能明白,哦,原來是這麼回事兒就好了。
react提供了react-router組件來幫助咱們實現路由功能。
可是react-router是一個不太好講的知識點。由於因爲react-router 4進行了顛覆性的更新,致使了react-router 3與react-router 4的使用方式大不同。也正是因爲變化太大,因此不少項目仍然正在使用react-router3,而且沒有過渡到react-router4的打算。
所以這裏我就很少講,提供一些參考學習資料。
未完待續
因爲時間關係,暫時就只能寫到這裏了。
原本還寫了一個比較完整的例子也在這篇文章裏逐步分析如何實現的,可是時間確實不夠。因此若是以爲看了上面的知識還想進一步學習的話,能夠先去https://github.com/yangbo5207/advance15 看看這個完整例子的樣子。
另外我曾經寫了一篇如何快速掌握一門前端框架,但願你們能夠參考參考。
按照個人計劃,只要理解了上面我所提到的知識,並把我準備的這個完整例子理解了。那麼你的react掌握程度也算是小有所成了。至少應屆畢業生找工做能提到這些思惟方式應該會頗有幫助。