客戶端高性能組件化框架React簡介、特點、環境搭建及常用語法

【本文源址: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-loaderbabel-core
還要安裝babel-preset-es2015babel-preset-react
用於解析ES6語法和React的JSX語法
還有reactreact-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的輸出
    • componentDidMount( )(服務器端不會調用)
      • 真實DOM被渲染後調用,可通過this.getDOMNode()訪問到真實的DOM元素
        此時可使用其他類庫來操作該DOM
  • 組件存在期Updateing【組件更新時觸發】(state,props變化觸發)
    • componentWillReceiveProps( )*
      • 組件接收新props時調用,並將其作爲參數nextProps使用,此時可以更改組件props及state
    • shouldComponentUpdate( )*
      • 組件是否應當渲染新props或state
        返回false表示跳過後續生命週期方法(通常不需要使用以避免出現bug)
        在出現應用瓶頸時,可通過該方法進行適當的優化。
        在首次渲染期間或者調用了forceUpdate方法後,該方法不會被調用
    • componentWillUpdate( )
      • 接收到新props或state後,進行渲染前調用,此時不允許更新props或state
    • render( )
      • 不再贅述
    • componentDidUpdate( )*
      • 完成渲染新的props或state後調用,此時可以訪問到新的DOM元素
  • 組件銷燬期Unmounting【組件銷燬時觸發】
    • componentWillUnmount()*
      • 組件移除前調用,可用於做一些清理工作
        在componentDidMount中添加的所有任務都需要在該方法中撤銷(e.g.定時器、事件監聽器等等)


附上一張我盜的圖,幫助大家理解(手動滑稽)

關於這些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]
否則會報錯的


==主頁傳送門==

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章