從零開始——React基礎入門(虛擬DOM、JSX語法、組件化開發、生命周期、單項數據流)

React最初來自Facebook內部的廣告系統項目,項目實施過程中前端開發遇到了巨大挑戰,代碼變得越來越臃腫且混亂不堪,難以維護。他們又對市場的現有的前端mvc框架都不滿意。于是他們決定拋開很多所謂的“最佳實踐”,重新思考前端界面的構建方式,就決定自己寫一套框架來解決這些問題,然后就有了React。

后來發現React框架在開發中很好用,于是FB投入了更多的人力去開發這套框架,最后在2013年5月宣布開源。

由于 React 的設計思想極其獨特,屬于革命性創新,性能出眾,代碼邏輯卻非常簡單。所以,越來越多的人開始關注和使用,認為它可能引領未來用戶界面開發的主流框架。

React 這么火,那么它到底有什么牛逼的地方?

特點及優勢

1.虛擬dom (開發時候不需要在頁面中寫任何dom元素)

2.jsx語法(寫頁面時候使用javascript xml格式的語法)

3.組件化開發(react最核心的思想是將頁面中任何一個區域或者元素都看成是一個組件 Component)

4.單向數據流(組件和后端之間的數據是單向的,從后端流動到react組件中)

5.組件生命周期(任何一個組件在dom中都具有一個完整的聲明周期,組件初始化的時候開始,組件被移除的時候消失,從而保證性能的優越)

虛擬DOM

虛擬DOM則是在DOM的基礎上建立了一個抽象層,我們對數據和狀態所做的任何改動,都會被自動且高效的同步到虛擬DOM,最后再批量同步到DOM中。

虛擬DOM會使得App只關心數據和組件的執行結果,中間產生的DOM操作不需要App干預,而且通過虛擬DOM來生成DOM,會有一項非常可觀收益——-性能。

React會在內存中維護一個虛擬DOM樹,當我們對這個樹進行讀或寫的時候,實際上是對虛擬DOM進行的。當數據變化時,然后React會自動更新虛擬DOM,然后拿新的虛擬DOM和舊的虛擬DOM進行對比,找到有變更的部分,得出一個Patch,然后將這個Patch放到一個隊列里,最終批量更新這些Patch到DOM中。

缺陷——首次渲染大量DOM時因為多了一層虛擬DOM的計算,會比innerHTML插入方式慢,所以使用時盡量不要一次性渲染大量DOM。

JSX語法
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <!-- 核心 -->
    <script src="build/react.min.js"></script>
    <!-- 渲染dom -->
    <script src="build/react-dom.min.js"></script>
    <!-- 把jsx、es6轉換成js、es5 -->
    <script src="build/browser.min.js"></script>
</head>
<body>
    <div id="root"></div>
    <!-- bebel是工具,把es6轉換成es5 -->
    <script type="text/babel">
            ReactDOM.render(<h1>React</h1>,document.getElementById("root"))
    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <!-- 核心 -->
    <script src="build/react.min.js"></script>
    <!-- 渲染dom -->
    <script src="build/react-dom.min.js"></script>
    <!-- 把jsx、es6轉換成js、es5 -->
    <script src="build/browser.min.js"></script>
</head>
<body>
    <div id="root"></div>
    <!-- bebel是工具,把es6轉換成es5 -->
    <script type="text/babel">
        var arr = ["張三","李四","王五"];
        ReactDOM.render(<div>{
            arr.map(function(ele,index){
                return <p>{ele}</p>
        })
        }</div>,document.getElementById("root"))
    </script>
</body>
</html>

JSX 的基本語法規則:遇到 HTML 標簽(以 < 開頭),就用 HTML 規則解析;遇到代碼塊(以 { 開頭),就用 JavaScript 規則解析。

組件

模塊化中的模塊一般指的是為了實現某些功能的代碼片段,模塊化主要實現了代碼分工,分模塊完成,強調的是功能;

組件是組成頁面的部件,內部封裝了組件的結構、樣式、行為,更多的考慮代碼的復用性,頁面結構的實現

在組件化開發中,一個模塊可能需要多個組件(比如評價的模塊,可能需要列表的組件、按鈕的組件、用戶信息組件…),但是有些模塊也可以跟組件沒有關系,只是實現一些功能。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <!-- 核心 -->
    <script src="build/react.min.js"></script>
    <!-- 渲染dom -->
    <script src="build/react-dom.min.js"></script>
    <!-- 把jsx、es6轉換成js、es5 -->
    <script src="build/browser.min.js"></script>
    <style type="text/css">
        *{
            margin: 0;
            padding: 0;
        }
        .header{
            position: fixed;left: 0;top: 0;height: 44px;border-bottom: 1px solid #ccc; text-align: center;width: 100%;line-height: 44px;
        }
        .footer{
             position: fixed;left: 0;bottom: 0;height: 44px;border-top: 1px solid #ccc; width: 100%;line-height: 44px;
             list-style: none;
        }
        .footer li{
            float: left;width: 25%;text-align: center;
        }
        .content{
            padding-top: 45px;padding-bottom: 45px;
        }
    </style>
</head>
<body>
    <div id="root"></div>
    <!-- bebel是工具,把es6轉換成es5 -->
    <!-- 組件都是用面向對象構建的,組件經常會被復用,用面向對象寫可以做到共用,組件名稱要大寫 -->
    <!-- 不能用class,class在js中是保留字符 -->
    <script type="text/babel">
    var Header = React.createClass({
        render:function(){
            return <div className="header">網站頭部</div>
        }
    })
    var Footer = React.createClass({
        render:function(){
            return <ul className="footer">
                <li>首頁</li>
                <li>列表</li>
                <li>購物車</li>
                <li>我的</li>
            </ul>
        }
    })
    var Content = React.createClass({
        render:function(){
            return <div className="content">內容</div>
        }
    })
    var IndexPage = React.createClass({
        render:function(){
            return <div className="page">
                <Header/>
                <Footer/>
                <Content/>
            </div>
        }
    })
    ReactDOM.render(<IndexPage/>,document.getElementById("root"))
    </script>
    <!-- 組件里面render的內容,就是當組件被調用顯示的內容 -->
</body>
</html>
單項數據流

props(properties 特性)是在調用時候被調用者設置的,只設置一次,一般沒有額外變化,可以把任意類型的數據傳遞給組件,盡可能的吧props當做數據源,不要在組件內部設置props。

組件傳參:
1.this.props.children
2.this.props.xxx

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <!-- 核心 -->
    <script src="build/react.min.js"></script>
    <!-- 渲染dom -->
    <script src="build/react-dom.min.js"></script>
    <!-- 把jsx、es6轉換成js、es5 -->
    <script src="build/browser.min.js"></script>
    <style type="text/css">
        *{
            margin: 0;
            padding: 0;
        }
        .header{
            position: fixed;left: 0;top: 0;height: 44px;border-bottom: 1px solid #ccc; text-align: center;width: 100%;line-height: 44px;
        }
        .footer{
             position: fixed;left: 0;bottom: 0;height: 44px;border-top: 1px solid #ccc; width: 100%;line-height: 44px;
             list-style: none;
        }
        .footer li{
            float: left;width: 25%;text-align: center;
        }
        .content{
            padding-top: 45px;padding-bottom: 45px;
        }
    </style>
</head>
<body>
    <div id="root"></div>
    <!-- bebel是工具,把es6轉換成es5 -->
    <!-- 組件都是用面向對象構建的,組件經常會被復用,用面向對象寫可以做到共用,組件名稱要大寫 -->
    <!-- 不能用class,class在js中是保留字符 -->
    <script type="text/babel">
    var Header = React.createClass({
        render:function(){
            return <div className="header">{this.props.title}</div>
        }
    })
    var Footer = React.createClass({
        render:function(){
            return <ul className="footer">
                <li>首頁</li>
                <li>列表</li>
                <li>購物車</li>
                <li>我的</li>
            </ul>
        }
    })
    var Content = React.createClass({
        render:function(){
            return <div className="content">{this.props.children}</div>
        }
    })
    var List = React.createClass({
        render:function(){
            return <ul>
                <li>商品1</li>
                <li>商品2</li>
                <li>商品3</li>
            </ul>
        }
    })
    var IndexPage = React.createClass({
        render:function(){
            return <div className="page">
                <Header title="首頁" />
                <Footer/>
                <Content>
                    <List/>
                </Content>
            </div>
        }
    })
    ReactDOM.render(<IndexPage/>,document.getElementById("root"))
    </script>
    <!-- 組件里面render的內容,就是當組件被調用顯示的內容 -->
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <!-- 核心 -->
    <script src="build/react.min.js"></script>
    <!-- 渲染dom -->
    <script src="build/react-dom.min.js"></script>
    <!-- 把jsx、es6轉換成js、es5 -->
    <script src="build/browser.min.js"></script>
    <style type="text/css">
        *{
            margin: 0;
            padding: 0;
        }
        .header{
            position: fixed;left: 0;top: 0;height: 44px;border-bottom: 1px solid #ccc; text-align: center;width: 100%;line-height: 44px;
        }
        .footer{
             position: fixed;left: 0;bottom: 0;height: 44px;border-top: 1px solid #ccc; width: 100%;line-height: 44px;
             list-style: none;
        }
        .footer li{
            float: left;width: 25%;text-align: center;
        }
        .content{
            padding-top: 45px;padding-bottom: 45px;
        }
    </style>
</head>
<body>
    <div id="root"></div>
    <!-- bebel是工具,把es6轉換成es5 -->
    <!-- 組件都是用面向對象構建的,組件經常會被復用,用面向對象寫可以做到共用,組件名稱要大寫 -->
    <!-- 不能用class,class在js中是保留字符 -->
    <script type="text/babel">
    var Header = React.createClass({
        render:function(){
            return <div className="header">{this.props.title}</div>
        }
    })
    var Footer = React.createClass({
        render:function(){
            return <ul className="footer">
                <li>首頁</li>
                <li>列表</li>
                <li>購物車</li>
                <li>我的</li>
            </ul>
        }
    })
    var Content = React.createClass({
        render:function(){
            return <div className="content">{this.props.children}</div>
        }
    })
    var List = React.createClass({
        render:function(){
            return <ul>
                {this.props.listData.map((ele,index)=>{
                    return <li>商品{ele}</li>
                })}
            </ul>
        }
    })
    var IndexPage = React.createClass({
        render:function(){
            return <div className="page">
                <Header title="首頁" />
                <Footer/>
                <Content>
                    <List listData={["1","2","3"]}/>
                </Content>
            </div>
        }
    })
    ReactDOM.render(<IndexPage/>,document.getElementById("root"))
    </script>
    <!-- 組件里面render的內容,就是當組件被調用顯示的內容 -->
</body>
</html>
組件的生命周期

隨著該組件的props(數據)或者state(狀態)發生改變,它的DOM表現也將有相應的變化,一個組件就是一個狀態機:對于特定的輸入,它總會返回一致的輸出。 React為每個組件提供了生命周期鉤子函數去響應不同的時刻,組件的生命周期分為三個部分:(1)實例化;(2)存在期;(3)銷毀&清理期。

鉤子函數類似有回調函數(callback),但他是在方法內容一開始調用的,而callback是在事件結束調用的。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <!-- 核心 -->
    <script src="build/react.min.js"></script>
    <!-- 渲染dom -->
    <script src="build/react-dom.min.js"></script>
    <!-- 把jsx、es6轉換成js、es5 -->
    <script src="build/browser.min.js"></script>
</head>
<body>
    <div id="root"></div>
</body>
<script type="text/babel">
    //getInitialState給組件設置默認的狀態
    //setState是React方法來修改state,視圖可以自動更新
    var Switch = React.createClass({
        getInitialState:function(){
            return{
                show:false
            }
            console.log("設置初始狀態")
        },
        changeShow:function(){
            this.setState({
                show:!this.state.show
            })
        },
        componentWillMount:function(){
            console.log("即將添加")
        },
        render:function(){
            return (
                <div id="warp" ref="warp">
                    <button onClick={this.changeShow} ref="btn">切換</button>
                    <div style={{display:this.state.show?'block':'none'}}>虛擬DOM,組件化開發,生命周期,jsx語法,單項數據流</div>
                </div> 
            )
        },
        componentDidMount:function(){
            //獲取真實dom,請求數據
            //this.refs或取到的是設置了ref屬性的dom集合
            console.log("添加完成")
            console.log(this.refs)
        }
    })
    ReactDOM.render(<Switch/>,document.getElementById("root"))
</script>
</html>

實例化階段(在組件調用之前的——創建階段)

getDefaultProps方法發生在創建組件類的時候,即調用React.createClass的時候。這個階段只會觸發一個getDefaultProps方法,給this.props作為該組件的默認屬性。

props屬性是一個對象,是組件用來接收外面傳來的參數的組件內部是不允許修改自己的props屬性的,只能通過父組件來修改。在getDefaultProps方法中,是可以設定props默認值的。

getInitialState 初始化組件的state的值,其返回值會賦值給組件的this.state屬性。對于組件的每個實例來說,這個方法的調用次數有且只有一次。

componentWillMount 此方法會在完成首次渲染之前被調用。這也是在render方法調用前可以修改組件state的最后一次機會。

render 生成頁面需要的虛擬DOM結構,用來表示組件的輸出。render方法需要滿足:(1)只能通過this.props和this.state訪問數據;(2)可以返回null、false或者任何React組件;(3)只能出現一個頂級組件;(4)必需純凈,意味著不能改變組件的狀態或者修改DOM的輸出。

componentDidMount 該方法發生在render方法成功調用并且真實的DOM已經被渲染之后,在該函數內部可以通過this.getDOMNode()來獲取當前組件的節點。然后就可以像Web開發中的那樣操作里面的DOM元素了。

存在期

用戶改如果變了組件的state,或者要展示的數據發生改變,這時候需要重新渲染組件。

componentWillReceiveProps 在任意時刻,組件的props都可以通過父輩組件來更改。當組件接收到新的props(這里不同于state)時,會觸發該函數,我們同時也獲得更改props對象及更新state的機會。

shouldComponentUpdate 該方法用來攔截新的props和state,然后開發者可以根據自己設定邏輯,做出要不要更新render的決定,讓它更快(當props 發生改變,或者調用了setState方法,調用了setState 方法哪怕state沒有改變也會觸發)

componentWillUpdate 與componentWillMount方法類似,組件上會接收到新的props或者state渲染之前,調用該方法。但是不可以在該方法中更新state和props。

render 生成頁面需要的虛擬DOM結構,并返回該結構。

componentDidUpdate 與componentDidMount類似,更新已經渲染好的DOM。

demo

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <!-- 核心 -->
  <script src="build/react.min.js"></script>
  <!-- 渲染dom -->
  <script src="build/react-dom.min.js"></script>
  <!-- 把jsx、es6轉換成js、es5 -->
  <script src="build/browser.min.js"></script>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/babel">
  var ProductList = React.createClass({
    getDefaultProps:function () {
      //設置默認數據
      return {
        listData:[]
      }
    },
    fnClick:function (index) {
      alert(index)
    },
    render:function () {
      return (
        <ul>
          {
            this.props.listData.map(function (ele,index) {
              return <li key={index} onClick={()=>this.fnClick(index)}>{ele}</li>
            }.bind(this))
          }
        </ul>
      )
    }
  });
  var IndexPage = React.createClass({
    render:function () {
      return (
        <div>
          <header>頭部</header>
          <ProductList listData={[1,2,3,4]}/>
          <footer>底部</footer>
        </div>
      )
    }
  });
  ReactDOM.render(<IndexPage/>,document.getElementById("root"));
</script>
</html>
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,234評論 2 375

推薦閱讀更多精彩內容