1. 程式人生 > >ReactJS入門(一)—— 初步認識React

ReactJS入門(一)—— 初步認識React

1. 虛擬DOM —— 在DOM樹的狀態需要發生變化時,虛擬DOM機制會將同一Event loop前後的DOM樹進行對比(自然通過一系列高效的演算法),如果倆個DOM樹存在不一樣的地方,那麼React僅僅會針對這些不一樣的區域(DOM diff)來進行響應的DOM修改,從而實現最高效的DOM操作和渲染。

如下圖,我們修改了DOM樹上一些節點(或UI元件)對應繫結的state(不知道state是什麼沒關係,後續我們會提及),React會即刻將其標記為“髒狀態”,在一個Event loop結束時(即使過程中你對某個元件的state進行了多次修改),React會計算得出DOM樹上需要修改的地方(“髒”了的地方,如下圖紅點)及其最終的狀態,並僅僅針對這些地方進行一次性的重新渲染。

於是好處顯而易見,並非每修改一次元件的state,就會重新渲染一次,而是在Event loop結束後做一次“秋後算賬”,減少冗餘的DOM操作。另外React只針對需要修改的地方來做新的渲染,而非重新渲染整個DOM樹,自然效率很是不錯。

2. 元件可巢狀,而且,可以模版化嘛 —— 其實在React裡提及的“元件”,常規是一些可封裝起來、複用的UI模組,說的接地氣了可以理解為“帶有細粒度UI功能的部分DOM區域”。然後我們可以把這些元件層層巢狀起來使用(當然這樣元件間會存在依賴關係)。

至於模組化,類似於ejs那樣可以作為獨立的模組被引用到頁面上來複用,不過咧,它可以直接把UI元件當作指令碼模組那樣來使用,咱完全可以配合CommonJS、AMD、CMD等規範來require需要的元件模組,並處理好它們的依賴關係(是不是碉堡了)。

基於上述的倆點,自然的也打算投入React懷抱了。不過在這之前得先理清倆點事情:

1. React是一個純View層,不擅長於和動態資料打交道,因此它不同於,也替代不了常規的MV*框架;

2. React很擅長於處理元件化的頁面,在頁面上搭元件的形式有點像搭樂高一樣,因此用上React的專案需求常規為介面元件化。另外React只支援到IE8+,就天朝的情況,是否使用React還是得稍微斟酌一番。

嘮嗑了這麼多,下面開始進入React的入門課程。本章提及的程式碼都可以在我的Github上下載到。

JSX

JSX是React編寫元件的一種語法規範,可以看為是js的擴充套件,它支援將HTML和JS混寫在一起,最終會通過React提供的 

JSXTransformer.js 來將其編譯為常規的js,方便瀏覽器解析。我們來個最簡單的例子:

複製程式碼
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>最簡單的jsx</title>
    <script src="react.js"></script>
    <script src="JSXTransformer.js"></script>
</head>
<body>
<div id="a">123</div>
<script type="text/jsx">
    var name = 'VaJoy';
    React.render(<h1>{name}</h1>, document.getElementById('a'));
</script>
</body>
</html>
複製程式碼

頁面上我們引入了 react.js 和 JSXTransformer.js 倆個重要的指令碼,前者自然是react的核心,後者則是將JSX語法轉為js語法。

實際上使用 JSXTransformer.js 在客戶端來解析JSX語法是一件冗餘、耗效能的事情,我們可以在上線專案之前,事先使用諸如 gulp-react 的工具先把所有的JSX均轉換為js檔案再引入到頁面中。

當然如果你掌握了react的JS寫法(當然相比JSX的學習成本要高不少),你可以直接在頁面上書寫相應的 原生js 即可,來個對比:

複製程式碼
//使用JSX
React.render(
    <div>
        <div>
            <div>content</div>
        </div>
    </div>,
    document.getElementById('example')
);

//不使用JSX
React.render(
    React.createElement('div', null,
        React.createElement('div', null,
            React.createElement('div', null, 'content')
        )
    ),
    document.getElementById('example')
複製程式碼

回到開頭第一段程式碼段,我們將頁面的JSX直接寫在 <script type="text/jsx"> 中,注意 type 型別要標明為 "text/jsx" 。

然後我們定義了個變數name,並使用了React最基礎和最常用的一個方法 React.render() 來渲染DOM:

    var name = 'VaJoy';
    React.render(<h1>{name}</h1>, document.getElementById('a'));

React.render() 支援兩個引數(其實還有第三個可選的引數,作為渲染後的回撥),第一個引數為模板的渲染內容(HTML形式),第二個引數表示要插入這段模板的DOM節點(DOM node)

這裡要提及一個知識點 —— 在JSX中,遇到 HTML 標籤(以 < 開頭),將用 HTML 規則解析;遇到程式碼塊(以 { 開頭),則用 JavaScript 規則解析。所以我們在<h1>中嵌入變數 name 時是以花括號的形式 {name} 來實現的。

至於執行結果,相信大家很容易猜出來:

即往div裡插入了(其實應該說是徹底替換為)JSX中的模板內容(<h1>元素)

鑑於JSX支援HTML跟JS的混寫,故其靈活性很高,我們試著把變數換為陣列:

複製程式碼
    var arr = ['HELLO', "here is", 123, "VaJoy`s blog"];
    React.render(
        <ul>{
            arr.filter(function(v){
                return typeof v === 'string'
            }).map(function(v){
                return <li> {v} </li>
            })
        }</ul>,
        document.getElementById('a')
    );
複製程式碼

結果如下:

元件

開頭已經提及React是用於元件化開發的,元件可看為是其最小組成單位,那麼什麼是元件(component)呢?我們看看下方這張圖:

這是一個移動端上的UI介面,用於查詢員工列表和某員工的具體資訊。那麼我們按頁面上各功能來細分,以左側的搜尋主頁介面而言,可以把它細分為SearchBar、EmployeeList、EmployeeListItem等UI元件,其中EmployeeList元件嵌套了多個EmployeeList元件,而這眾多元件的搭配組成了整個Hompage介面。

常規我們用React開發的頁面可以看為一個大元件,它由多個小元件搭建而成。

在JSX中,我們可以通過 React.createClass() 方法來建立一個元件(注意元件的名稱必須為大寫字母開頭),並以命名標籤的形式(<MyClassName />)來引用:

複製程式碼
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>元件</title>
    <style>
        .nameStyle{
            color: dodgerblue;
        }
    </style>
    <script src="react.js"></script>
    <script src="JSXTransformer.js"></script>
</head>
<body>
<div id="a"></div>
<script type="text/jsx">
    var name = 'VaJoy';
    var Demo = React.createClass({  //隨便建了個叫Demo的元件
        render: function() {  //每個元件都需要有自己的render方法
            var a = "Hello,";
            return (
                <p className="nameStyle" >{a,name}</p> //當然你可以寫為{a}{name}
            );
        }
    });
    React.render(
        <Demo />,  //引入元件Demo
        document.getElementById('a')
    );
</script>
</body>
</html>
複製程式碼

注意每個元件都需要有一個render方法,用於輸出元件內容。另外元件DOM元素上的 class 屬性需要寫成 className ,for 屬性需要寫成 htmlFor ,這是因為 class 和 for 是 JavaScript 的保留字。

執行效果如下:

可以看到,如果在一個容器中引用了多個變數,React會對應每個文字節點來自動生成對應span標籤(React防止XSS的機制,因此要注意規避span的樣式汙染!)

再來看個元件巢狀的示例,其實都很簡單:

 View Code

注意在JSX裡,給DOM設定 style 必須寫成 {{ marginRight : '10px', fontSize : '18px' }} 的對映形式(預防XSS),否則會報錯

執行結果(注意連空格都生成了一個span)

元件的“資料”

我們在前頭提及了,React只是一個純view層,並不涉及model。不過但對於React元件而言,它有兩種特殊的data —— Props 和 States。其中的 States 有點類似於各MV*框架中的model部分,可以稱為React的狀態機制,用於與使用者進行互動。

1. Props

props表示元件自身的屬性,也可以用於在巢狀的內外層元件中傳遞資訊(常規是由父層元件傳遞給子層元件)。要注意它是不變的、也不應該嘗試去改變的。我們來個簡單的示例:

複製程式碼
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>元件Props</title>
    <script src="react.js"></script>
    <script src="JSXTransformer.js"></script>
</head>
<body>
<div id="a"></div>
<script type="text/jsx">
    var Component1 = React.createClass({
        render: function() {
            return <p> {this.props.abc, this.props.name} </p>;
        }
    });
    var Component2 = React.createClass({
        render: function() {
            return (
                    <div className="commentList" onClick={this.handleClick}>
                        <Component1 abc="你好!" name="張三" />
                        <Component1 abc="Hi!" name="李四" />
                    </div>
            );
        },
        handleClick: function (e) {
            console.log(this.props.name, e.target);
        }
    });
    React.render(
        <Component2 name="我是Component2的name哦!" />,
        document.getElementById('a')
    );
</script>
</body>
</html>
複製程式碼

這裡我們註冊了倆個元件類 Component1 和 Component2 ,其中 Component2 內嵌了 Component1 。

我們先看 Component1 的定義:

複製程式碼
    var Component1 = React.createClass({
        render: function() {
            return <p> {this.props.abc, this.props.name} </p>;
        }
    });
複製程式碼

它返回了一個p標籤,其中的內容是 this.props.abc 和 this.props.name,這裡的 this 指的是元件 Component1 本身,因此比如 this.props.name 獲取到的便是元件 Component1 自身的屬性 name 的值(abc 和 name 的值我們都在 Component2 中進行了定義)。

我們接著看 Component2:

複製程式碼
    var Component2 = React.createClass({
        render: function() {
            return (
                    <div className="commentList" onClick={this.handleClick}>
                        <Component1 abc="你好!" name="張三" />
                        <Component1 abc="Hi!" name="李四" />
                    </div>
            );
        },
        handleClick: function (e) {
            console.log(this.props.name, e.target);
        }
    });
複製程式碼

在這裡我們除了嵌入元件 Component1 並定義了它們的屬性值 abc 和 name ,也使用了React的事件機制——我們用 onClick 方法來觸發點選事件,其對應的 this.handleClick 是我們自定義的一個方法,用於輸出 Component2 的 name 屬性和當前被點選的目標元素。

最後我們用 React.render 方法渲染元件 Component2 ,並定義了它的 name 屬性:

    React.render(
        <Component2 name="我是Component2的name哦!" />,
        document.getElementById('a')
    );

執行結果如下:

這裡有點要留意的,無論是 props 元件屬性,或者是 onClick 事件,都是 React 內部的東西,我們在HTML上是看不到它們的:

順便提個Props的語法糖 —— 我們可以通過 “...this.props” 來將父層元件繫結的全部屬性都直接寫到子元件中:

 View Code

執行結果:

Props也並非都是直接寫在元件標籤上的屬性,有一個例外 —— props.children,它表示當前元件的所有子節點(它們常規是在外部的元件賦予的)

複製程式碼
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>props.children</title>
    <style>
        p:active{ color: deeppink; }
    </style>
    <script src="react.js"></script>
    <script src="JSXTransformer.js"></script>
</head>
<