【本文源址:http://blog.csdn.net/q1056843325/article/details/54729657 轉載請添加該地址】
明天就是除夕了
預祝大家新春快樂 [ ]~( ̄▽ ̄)~*
天天飯局搞得我是身心疲憊= =
所以更新比較慢
今天想跟大家分享的就是這個大名鼎鼎的React框架
簡介
React是近兩年非常流行的框架
流行到什麼程度呢?
我看了一下Github上的數據
React達到了5w8+的star
在JavaScript中star排名第4
受歡迎程度可見一斑
感興趣的同學,給大家設置一個傳送門:Github-JavaScript-most stars
React並不難,還是挺容易上手的
起源於Facebook內部項目(一個廣告系統)
傳統頁面從服務器獲取數據,顯示到瀏覽器上,用戶輸入數據傳入服務器
但隨着數據量增大,越來越難以維護了
Facebook覺得MVC不能滿足他們的擴展需求了(巨大的代碼庫和龐大的組織)
每當需要添加一項新功能或特性時,系統複雜度就呈幾何增長
致使代碼脆弱不堪、不可預測,結果導致他們的MVC正走向崩潰
當系統中有很多的模型和相應視圖時,其複雜度就會迅速擴大,非常難以理解和調試
總之就是Facebook對市場上所有JS-MVC框架都不滿意,認爲都不適合大規模應用
就自己寫了一套,用來架設Instagram網站
寫完後用着用着,發現哎呦這貨還真是不錯,然後就開源了
隨着這幾年的沉澱,React已經變得越來越強大了
值得我們去了解~
MVC
科普一下MVC
MVC是一種軟件架構模式(後端影響到了前端)
MVC就分爲M、V、C三部分
- Model(模型):
應用程序中用於處理應用程序數據邏輯的部分,通常負責在數據庫中存取數據 - View(視圖):
應用程序中處理數據顯示的部分,通常依據模型數據創建 - Controller(控制器):
應用程序中處理用戶交互的部分,通常負責從視圖讀取數據,控制用戶輸入,並向模型發送數據
簡單的理解一下
我們就是user,在頁面中點擊了一個按鈕觸發了事件
控制器Controller調整數據,模型Model中數據改變
數據改變又會導致視圖View更新
UI的改變反饋呈現給我們user
這裏我要特別說明一下,雖然這裏介紹了MVC
但是不能說React就是MVC框架
可以說React是用於構建組件化UI的庫,是一個前端界面開發工具
它可以作爲MVC中的View視圖部分
框架特點
React它具有以下特點
- 高性能
傳統web頁面操作DOM涉及重繪重排相當耗性能
React 提供了一種不同而又強大的方式來更新Dom(輕量級虛擬Dom——Virtual Dom),代替直接操作DOM
更新virtual dom時不一定馬上影響真實Dom,React會等到事件循環結束
利用Diff算法,通過當前新Dom表述與之前做比較,計算出最小步驟來更新真實Dom - 組件化
Dom樹上的節點稱爲元素,而虛擬Dom 的節點稱作組件(可複用性)
(組件的特點下面還會談到) - 可預測性
state屬性包含定義組件所需要的一些數據,當數據發生變化時,將會調用render重現渲染
React 把組件看成是一個狀態機(State Machines)
通過與用戶的交互,實現不同狀態,然後渲染 UI,讓用戶界面和數據保持一致 - 單向數據流
數據從父節點傳遞到子節點,只需要從父節點獲取props渲染即可
(往下看就理解了)
環境搭建
我選擇使用webpack搭建環境,當然其他工具也可以
這是我的webpack.config.js配置文件
module.exports = {
entry: {
index: './src/js/entry.js'
},
output: {
path: './static/dist/',
publicPath: 'http://localhost:8080/static/dist/',
filename: '[name].js'
},
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel',
query: {
presets: ['react', 'es2015']
}
},
{
test: /.less$/,
loader: 'style!css!less'
}
]
}
}
這裏的關鍵就是除了要安裝babel-loader
和babel-core
還要安裝babel-preset-es2015
和 babel-preset-react
用於解析ES6語法和React的JSX語法
還有react
和react-dom
也是必須要下載的依賴
全部依賴模塊在這裏
"devDependencies": {
"babel-core": "^6.22.1",
"babel-loader": "^6.2.10",
"babel-preset-es2015": "^6.22.0",
"babel-preset-react": "^6.22.0",
"css-loader": "^0.26.1",
"less": "^2.7.2",
"less-loader": "^2.2.3",
"react": "^15.4.2",
"react-dom": "^15.4.2",
"style-loader": "^0.13.1",
"webpack": "^1.14.0",
"webpack-dev-server": "^1.16.2"
}
JSX
簡單說一下React的JSX語法是個神馬東西
可能一會兒大家會看大不明覺厲的代碼
比如
return (
<div>hehe<div>
)
這就是JSX代碼,它是React提供的語法糖
是React的重要組成部分,使用類似XML標記的方式來聲明界面及關係
語法糖的意思我寫ES6的時候也說了
就是計算機語言中添加的語法,對語言的功能沒影響
方便我們開發人員使用的,可以增強可讀性
如果使用JS代碼也可以,不過官方推薦使用JSX
這樣結構層次關係都很清晰
webpack會幫我們把他們轉換成瀏覽器認識的js代碼(loader的作用)
(如果好奇轉換成了什麼,可以去webpack輸出文件查看,或者找jsx轉js的工具)
JSX語法結構說的通俗一點就是HTML、JS混寫
可能大家會有疑惑,說好的結構、樣式、行爲相分離的前端思想呢?!
React其中一個主要的設計理念是編寫簡單且容易理解的代碼
但我們爲了實現組件化確實不方便松耦合
大家也不要過分較真
這樣的語法結構是怎樣解析的呢?其實並不神奇
JSX的語法規則:
- 遇到 HTML 標籤(以 < 開頭),就使用 HTML 規則解析
- 遇到代碼塊(以 { 開頭),就使用 JavaScript 規則解析
- 代碼塊中如果只有一個數組變量,就展開這個數組的所有成員
不理解不要慌,看了下面就懂了
提前滲透一下
渲染到頁面
終於寫到語法正題了
在此之前我們必須要引用的兩個對象
一個React核心對象和一個React-Dom對象
(這裏就先不使用ES6的語法了)
var React = require('react');
var ReactDom = require('react-dom');
ReactDom.render()是react最最基本的方法
所以我放到最開始來講
它通過ReactDom將我們的組件渲染到頁面
我在頁面中添加一個節點
<div id="root"></div>
現在頁面中什麼也沒有
不過馬上就有了
ReactDom.render(
<h1>Demo</h1>,
document.getElementById('demo')
);
第一個參數是要插入的組件(不過這裏我們先插入一個DOM節點)
第二個參數就是要渲染的DOM節點
(React不建議直接添加到body標籤document.body,不相信的話可以試一下會警告)
頁面中出現了“Demo”
實際上react將我們的節點插入到了div節點的內部
組件化
React的一大特點就是組件化
React組件Component有以下特點
- 組合:簡單組件可組合爲複雜組件
- 重用:組件獨立,可被多個組件使用
- 測試:組件獨立,便於測試
- 維護:UI和組件相關邏輯都封裝在組件內部,便於維護
組件生成
React.createClass()就是用於將代碼封裝成組件的方法
它會生成一個React組件
var App = React.createClass({
render: function(){
return (
<p>This is a component...</p>
)
}
});
這個方法參數是一個對象
對象中有一個render返回一個組件
render是輸出組件必須要寫的(關於它下面還會再說)
先記住兩點
- 組件名首字母一定大寫,否則會報錯
- 輸出的組件只能有一個頂級標籤,其他標籤會失效
所以,各位,下面的寫法都是不對的
//錯誤的寫法:變量名首字母沒大寫
var app = React.createClass({
render: function(){
return <p>This is a component...</p>
}
})
//錯誤的寫法:存在多個頂級標籤
var App = React.createClass({
render: function(){
return (
<p>This is a component...</p>
<p>This is also a component...</p>
)
}
});
組件中html語法兩邊加括號的目的
是爲了
防止JavaScript自動分號機制產生問題
組件渲染
生成的組件要想渲染到頁面
就使用我們剛剛提到的的ReactDom.render( )
ReactDom.render(
<App></App>,
document.getElementById('root')
);
組件要寫成標籤的形式
這裏我們就要寫<App></App>
或者單標籤形式<App/>
也可以
組件特性
爲了加以區分,我把html標籤的屬性叫組件特性
var App = React.createClass({
render: function(){
return <p name="demo">This is a component...</p>;
}
});
ReactDom.render(
<App></App>,
document.getElementById('root')
);
還要注意兩個特例
- class要寫成className
- for要寫成htmlFor
因爲他們是JavaScript的保留字
如果想要爲組件添加內聯樣式,可以這樣寫
var App = React.createClass({
render: function(){
var styles = {
color: '#fff',
backgroundColor: '#000'
}
return <p className="demo" style={styles}>This is a component...</p>; // <--
}
});
ReactDom.render(
<App></App>,
document.getElementById('root')
);
聲明瞭一個styles對象
但是將它添加到屬性時,要使用 { }
因爲JSX語法中,html中使用js就必須使用大括號
關於這一點下面就不再贅述了
組件屬性
this.props
組件的屬性同樣可以像html一樣添加<App name="payen"></App>
並且這個組件屬性內部可以通過this.props對象獲取
var App = React.createClass({
render: function(){
return <p>name:{this.props.name} age:{this.props.age}</p>;
}
});
ReactDom.render(
<App name="payen" age="20"></App>,
document.getElementById('root')
);
瞭解了這個,我們可以做一個小練習
現在有一組數據,利用它組成一個有序列表組件
var data = ['Mr.A','Mr.B','Mr.C'];
可以將這個數組成爲組件屬性
然後利用this.props.data獲取數據
最後使用ES5數組的map方法就大功告成了
var List = React.createClass({
render: function(){
return (
<ol>
{
this.props.data.map(function(item, index){
return <li key={1000 + index}>{item}</li>;
})
}
</ol>
)
}
});
ReactDom.render(
<List data={data}></List>,
document.getElementById('root')
);
還要注意<li key={1000 + index}>{item}</li>
key值如果不寫的話,雖然可以正常渲染
但會警告我們數組或迭代器的每一項都應該有一個獨一無二的key值
這裏我就使用了1000加上索引的形式添加了key值
this.props.children
通常組件的屬性與this.props對象中的屬性是一一對應的
但有一個例外,它是this.props.children
它表示我們組件的所有子節點
什麼意思呢?接着我們上面的例子
我們在List組件中添加一些子節點
修改ReactDom.render( )方法的參數
ReactDom.render(
<List data={data}>
<span>Mr.D</span>
<span>Mr.E</span>
</List>,
document.getElementById('root')
);
我們發現頁面中並沒有什麼變化,但瀏覽器也沒有報錯
這時我們需要使用this.props.children
var data = ['Mr.A','Mr.B','Mr.C'];
var List = React.createClass({
render: function(){
console.log(this.props.children);
return (
<ol>
{
this.props.data.map(function(item, index){
return <li key={1000 + index}>{item}</li>;
})
}
{
this.props.children
}
</ol>
)
}
});
ReactDom.render(
<List data={data}>
<span>Mr.D</span>
<span>Mr.E</span>
</List>,
document.getElementById('root')
);
如此頁面中就顯示出了子節點
這個this.props.children很奇怪,它有三種類型值
- 沒有子節點,值爲undefined
- 有一個子節點,值爲object對象
- 有多個子節點,值爲array數組
(可以在控制檯上輸出驗證)
所以我們處理它要特別小心
好在我們可以使用React給我們提供的方法
利用React.Children.map( )我們就可以放心遍歷處理子節點
var data = ['Mr.A','Mr.B','Mr.C'];
var List = React.createClass({
render: function(){
return (
<ol>
{
this.props.data.map(function(item, index){
return <li key={1000 + index}>{item}</li>;
})
}
{
React.Children.map(this.props.children,function(child){
return <li>{child}</li>
})
}
</ol>
)
}
});
ReactDom.render(
<List data={data}>
<span>Mr.D</span>
<span>Mr.E</span>
</List>,
document.getElementById('root')
);
組件驗證
組件的屬性可以接受任何值,數字、字符串、函數、對象什麼都可以
但有時候,我們拿到一個組件,想要驗證參數是否符合我們的要求(這其實很重要,不要輕視)
這時就需要使用組件的propTypes( )方法和React.PropTypes配合驗證了
var data = ['Mr.A','Mr.B','Mr.C'];
var App = React.createClass({
propTypes: {
data: React.PropTypes.array
},
render: function(){
return (
<div>{this.props.data}</div>
)
}
});
ReactDom.render(
<App data={data}></App>,
document.getElementById('root')
);
這裏我期望的data屬性值爲array數組類型
沒有任何問題,因爲我們傳入的就是數組
可是如果改成期望字符串類型data: React.PropTypes.string
瀏覽器就會發出警告
詳細見React中文官網:Prop 驗證
組件嵌套
還記得React單向數據流的特點麼
也就是說我們應該把數據傳遞給父節點
父節點通過this.prop將數據傳遞給子節點
子節點再通過自己的this.prop處理收到的數據
var data = ['Mr.A','Mr.B','Mr.C'];
var List = React.createClass({
render: function(){
return (
<ol>
{
this.props.data.map(function(item, index){
return <li key={1000 + index}>{item}</li>;
})
}
</ol>
)
}
});
var App = React.createClass({
render: function(){
return (
<div>
<List data={this.props.data}></List>
</div>
)
}
});
ReactDom.render(
<App data={data}></App>,
document.getElementById('root')
);
所呈現的DOM結構
生命週期
生命週期不難理解
組件的一生無非就是產生、更新、銷燬
在組件的每一個生命週期內,都會按順序觸發一些組件方法
比如我們剛剛的render方法就會在產生和更新的階段都會觸發
具體觸發的回調函數API以及作用給大家整理在下面
(關於它們在整個生命週期的觸發次數大家應該都能想明白就不寫了)
(不常用的我在後面的標註了*號)
- 組件實例化Mouting【組件生成時觸發】
- getDefaultProps( )
- 作用於組件類,返回對象用於設置默認的this.props(引用值會在實例中共享)
- e,g.
return {name: 'payen'}
相當於初始化了組件屬性this.props.name = 'payen'
- getInitialState( )
- 作用於組件的實例,返回對象作爲this.state的初始值
- e,g.
return {show: false}
相當於初始化了組件狀態this.state.show = false
- componentWillMount( )
- 首次渲染前調用,可做一些業務初始化操作,也可以通過this.setState()設置組件狀態
- e,g.
this.setState({show: false})
- render( )
- 必選方法,用於創建虛擬DOM,有特殊規則(再囉嗦一遍)
- 只能通過this.props和this.state訪問數據
- 可以返回null、false或任何React組件
- 只能出現一個頂級組件(不能返回數組)
- 不能改變組件的狀態
- 不能修改DOM的輸出
- 必選方法,用於創建虛擬DOM,有特殊規則(再囉嗦一遍)
- componentDidMount( )(服務器端不會調用)
- 真實DOM被渲染後調用,可通過this.getDOMNode()訪問到真實的DOM元素
此時可使用其他類庫來操作該DOM
- 真實DOM被渲染後調用,可通過this.getDOMNode()訪問到真實的DOM元素
- getDefaultProps( )
- 組件存在期Updateing【組件更新時觸發】(state,props變化觸發)
- componentWillReceiveProps( )*
- 組件接收新props時調用,並將其作爲參數nextProps使用,此時可以更改組件props及state
- shouldComponentUpdate( )*
- 組件是否應當渲染新props或state
返回false表示跳過後續生命週期方法(通常不需要使用以避免出現bug)
在出現應用瓶頸時,可通過該方法進行適當的優化。
在首次渲染期間或者調用了forceUpdate方法後,該方法不會被調用
- 組件是否應當渲染新props或state
- componentWillUpdate( )
- 接收到新props或state後,進行渲染前調用,此時不允許更新props或state
- render( )
- 不再贅述
- componentDidUpdate( )*
- 完成渲染新的props或state後調用,此時可以訪問到新的DOM元素
- componentWillReceiveProps( )*
- 組件銷燬期Unmounting【組件銷燬時觸發】
- componentWillUnmount()*
- 組件移除前調用,可用於做一些清理工作
在componentDidMount中添加的所有任務都需要在該方法中撤銷(e.g.定時器、事件監聽器等等)
- 組件移除前調用,可用於做一些清理工作
- componentWillUnmount()*
附上一張我盜的圖,幫助大家理解(手動滑稽)
關於這些API更詳細的信息
建議大家可以去React中文官網查看:Component Specs and Lifecycle
組件狀態
上面提到了this.state,和我們之前介紹的this.props一樣重要
不過this.props通常不會變,但this.state會變
就如其字面意思,表示組件的狀態
這個屬性是隻讀的
所以設置狀態我們需要使用this.setState( )
可使用this.setState( )的方法:
componentWillMount、componentDidMount、componentWillReceiveProps
組件交互
在此之前我們需要了解的就是React的事件系統
JavaScript原始行間綁定事件都是普遍小寫<button onclick="clickHandle()"></button>
但我們在React中要使用駝峯寫法<button onClick="clickHandle()"></button>
React的事件處理器會傳入虛擬事件對象的實例(一個對瀏覽器本地事件的跨瀏覽器封裝)
它有和瀏覽器本地事件相同的屬性和方法,包括 stopPropagation() 和 preventDefault(),
但是沒有瀏覽器兼容問題
詳細支持事件見中文官網:事件系統-支持的事件
現在我們要來實現這樣一個簡單的功能
點擊按鈕,出現彈框
單擊彈框,彈框消失
先來實現結構與樣式
var App = React.createClass({
render: function(){
return (
<div>
<button>點擊</button>
<PopUp></PopUp>
</div>
)
}
});
var PopUp = React.createClass({
render: function(){
var styles = {
position: 'absolute',
left: '40px',
top: '40px',
width: '100px',
height: '100px',
backgroundColor: '#f40'
}
return (
<div className="popup" style={styles}></div>
)
}
})
ReactDom.render(
<App/>,
document.getElementById('root')
);
首先我們先來實現第一個功能:點擊按鈕出現彈框
問題是改如何實現
我們的React是單向數據流
父級向子級傳遞數據
最好的辦法就是在父級設置組件狀態this.state
將狀態通過組件屬性this.props傳遞給子級
這樣點擊事件要做的就是改變父級狀態
子級狀態也會隨之改變
var App = React.createClass({
getInitialState: function(){
return {
open: false
}
},
buttonHandler: function(){
this.setState({
open: true
});
},
render: function(){
return (
<div>
<button onClick={this.buttonHandler}>點擊</button>
<PopUp open={this.state.open}></PopUp>
</div>
)
}
});
var PopUp = React.createClass({
render: function(){
var styles = {
position: 'absolute',
left: '40px',
top: '40px',
width: '100px',
height: '100px',
backgroundColor: '#f40'
}
if(this.props.open){
styles.display = 'block';
}else{
styles.display = 'none';
}
return (
<div className="popup" style={styles}></div>
)
}
})
ReactDom.render(
<App/>,
document.getElementById('root')
);
第一個功能實現了,再來看第二個
點擊彈窗讓其消失
同樣子級的顯示與否掌控在父級手裏
要向讓子級消失,就必須要改變父級的組件狀態this.state
所以我們必須要把事件函數綁定在父級
再利用組件屬性this.props將其傳遞給子級
完整代碼如下
var App = React.createClass({
getInitialState: function(){
return {
open: false
}
},
buttonHandler: function(){
this.setState({
open: true
});
},
popupHandler: function(){
this.setState({
open: false
});
},
render: function(){
return (
<div>
<button onClick={this.buttonHandler}>點擊</button>
<PopUp open={this.state.open} handler={this.popupHandler}></PopUp>
</div>
)
}
});
var PopUp = React.createClass({
render: function(){
var styles = {
position: 'absolute',
left: '40px',
top: '40px',
width: '100px',
height: '100px',
backgroundColor: '#f40'
}
if(this.props.open){
styles.display = 'block';
}else{
styles.display = 'none';
}
return (
<div className="popup" style={styles} onClick={this.props.handler}></div>
)
}
})
ReactDom.render(
<App/>,
document.getElementById('root')
);
用一句話來總結一下,那就是數據都交給父級來管理
獲取真實DOM節點
我們已經知道了
創建的組件都是虛擬DOM節點
只有當它渲染到了頁面,纔會成爲真正的DOM節點
但是有些時候,我們需要獲取到真正的DOM節點
這時需要先設置標籤ref屬性,再利用組件的this.refs對象獲取
還是通過一個小例子來解釋
現在要實現這樣一個功能
在輸入欄中輸入字符並在外部實時輸出
我們要獲取的真實DOM節點就是input中的輸入字符串
步驟也很簡單,完整代碼如下
var Input = React.createClass({
getInitialState: function(){
return {
val: ''
}
},
changeHandler: function(){
this.setState({
val: this.refs.node.value
});
},
render: function(){
return (
<div>
<input type="text" ref="node" onChange={this.changeHandler}/>
<p>{this.state.val}</p>
</div>
)
}
});
ReactDom.render(
<Input/>,
document.getElementById('root')
);
我爲input標籤設置了ref值爲node
可以把它理解爲爲這個節點起了個小名“node”
那麼this.refs.node
就可以引用這個真實的節點<input/>
通過綁定一個change事件
我們的輸入每次改變都會改變組件的狀態state
state改變,value就會渲染到頁面
獲取真實DOM節點還有一個不常用的方法
比如在我們的例子中可以把input標籤改成這樣
<input type="text" ref={function(dom){this._node = dom}.bind(this)} onChange={this.changeHandler}/>
向ref屬性中添加一個匿名函數
這個函數的參數就是真實DOM節點
我們可以把它保存下來,比如做爲組件的_node屬性
不要忘了改變this的指向
事件觸發函數就可以通過this._node獲取真正的DOM節點
changeHandler: function(){
this.setState({
val: this._node.value
});
}
還要注意的一點是
這個真·DOM節點的獲取
必須要等到虛擬DOM插入文檔以後,才能使用屬性this.refs.[ref-name]
否則會報錯的