精益 React 學習指南 (Lean React)- 1.3 React 組件

書籍完整目錄html

1.3 React 組件

圖片描述

1.3.1 React 組件介紹

在 React 中組件是第一元素,是 React 的基礎,一個 React 應用就是基於 React 組件的組合而成。
前面的 JSX 練習事後,你們應該對 React 組件不陌生了,在這一節咱們將溫習以及深刻學習 React 組件。node

1.3.2 建立一個 React 組件

建立一個 React 組件的方法爲,調用 React.createClass 方法,傳入的參數爲一個對象,對象必須定義一個 render 方法,render 方法返回值爲組件的渲染結構,也能夠理解爲一個組件實例(React.createElement 工廠方法的返回值),返回值有且只能爲一個組件實例,或者返回 null/false,當返回值爲 null/false 的時候,React 內部經過 <noscript/> 標籤替換。react

eg:git

var MyComponent = React.createClass({
        render: function() {
            return <p>....</p>;
        }
    });

組件命名空間

能夠看出 React.createClass 生成的組件類爲一個 Javascript 對象。 當咱們想設置命名空間組件時,能夠在組件下面添加子組件:github

eg:segmentfault

MyComponent.SubComponent = React.createClass({...});
    MyComponent.SubComponent.Sub = React.createClass({....});

在組件較多的狀況下,能夠藉助命名空間優化組件維護結構以及解決組件名稱衝突問題。數組

無狀態組件

除了能夠經過 React.createClass 來建立組件之外,組件也能夠經過一個普通的函數定義,函數的返回值爲組件渲染的結果。緩存

eg:babel

function StatelessComponent(props) {
        return  <div> Hello {props.name} </div>
    }

無狀態組件可以優化性能,由於其內部不會維護狀態,React 內部不會有一個對應的組件實例,而且也沒有生命週期 hook。app

1.3.3 將組件渲染到 DOM 中

當建立好了組件事後,爲了將組件渲染到 DOM 中顯示出來,須要兩個步驟

  1. 在 HTML 中定義一個元素,設置 id 屬性

  2. JSX 中調用 ReactDOM.render 方法, 第一個參數爲 組件,第二個爲剛纔定義的 DOM 元素

eg:

<!-- 定義的 DOM 元素 -->
<div id="example"></div>
<script type="text/babel">
    // 自定義組件 
    var MyComponent = React.createClass({
        render: function() {
            return <p>....</p>;
        }
    });
    // 調用 render 方法
    ReactDOM.render(
    <MyComponent/>,
    document.getElementById('example')
    );
</script>

對於組件的渲染,可能涉及到的一些問題:

  • Q1: 只能 render 到一個元素嗎?

  • Q2: 在程序運行時可以動態的調用 render 嗎?

  • Q3: 修改了數據事後,還須要從新調用 render 方法嗎?

這裏要先提一下 React 的設計初衷,React 在開發時候的目標就是簡單精巧,能夠和其餘框架結合起來使用。簡單而言咱們能夠當 React 是一個渲染數據對象到 DOM 中的 Javascript 函數工具類,工具類固然能夠屢次使用。

那麼對於上面的問題:

  • A1: 不是,React 能夠渲染到多個元素,任意位置的元素。

  • A2: 能夠,好比以一個彈出層組件爲例,咱們但願建立一個 append 到 body 最後的組件來實現全屏遮罩, 那麼咱們能夠動態的建立一個 div append 到 body 最後,而後將彈出層 render 到那個 div 內。

  • A3: 假設每次數據改變都從新調用 render 方法,那麼每次 render 帶來的結果是從新建立一個頂級組件實例,以及子組件實例。 若是隻調用 render 一次,將數據的變動放在組件內部,那麼就不會重複建立頂級組件。

1.3.4 組件狀態 State

圖片描述

React 中每一個組件能夠存儲本身的當前狀態, 以一個 switch 開關組件爲例,開關的狀態能夠存儲在組件內部。

React 的渲染結果是由組件屬性和狀態共同決定的,狀態和屬性的區別是,狀態維護在組件內部,屬性是由外部控制,咱們先介紹組件狀態相關細節:

控制狀態的 API 爲:

  1. this.state:組件的當前狀態

  2. getInitialState:獲取組件的初始狀態,在組件加載的時候會被調用一次,返回值賦予 this.state 做爲初始值

  3. this.setState:

    • 組件狀態改變時,能夠經過 this.setState 修改狀態

    • setState 方法支持按需修改,如 state 有兩個字段,僅當 setState 傳入的對象包含字段 key 纔會修改屬性

    • 每次調用 setState 會致使重渲染調用 render 方法

    • 直接修改 state 不會重渲染組件

eg:

var Switch = React.createClass({
        // 定義組件的初始狀態,初始爲關
        getInitialState: function() {
            return {
                open: false
            }
        },
        // 經過 this.state 獲取當前狀態
        render: function() {
            console.log('render switch component');
            var open = this.state.open;
            return <label className="switch"> 
                        <input type="checkbox" checked={open}/> 
                    </label>
        },
        // 經過 setState 修改組件狀態
        // setState 事後會 React 會調用 render 方法重渲染
        toggleSwitch: function() {
            var open = this.state.open;
            this.setState({
                open: !open
            });
        }
    })

1.3.5 組件屬性 Props

前面已經提到過 React 組件能夠傳遞屬性給組件,傳遞方法和 HTML 中無異, 能夠經過 this.props 獲取組件屬性

屬性相關的 API 爲:

  1. this.props: 獲取屬性值

  2. getDefaultProps: 獲取默認屬性對象,會被調用一次,當組件類建立的時候就會被調用,返回值會被緩存起來,當組件被實例化事後若是傳入的屬性沒有值,會返回默認屬性值

  3. this.props.children:子節點屬性

  4. propTypes: 屬性類型檢查

以一個代辦事項的列表項組件爲例:

// props.name 表示代辦事項的名稱
    var TodoItem = React.createClass({
        render: function() {
            var props = this.props;
            return <div className="todo-item">
                        <span className="todo-item__name">{props.name}</span>
                    </div>
        }
    });

    ReactDOM.render(
        <TodoItem name="代辦事項1"/>, 
         document.getElementById('example'));

children 屬性

組件屬性中會有一個特殊屬性 children ,表示子組件, 仍是以上面一個組件爲例子,咱們能夠換一種方式定義 name:

var TodoItem = React.createClass({
        render: function() {
            var props = this.props;
            return <div className="todo-item">
                        <span class="todo-item__name">{props.children}</span>
                    </div>
        }
    });

    ReactDOM.render(
        <TodoItem>代辦事項1</TodoItem>, 
         document.getElementById('example'));

須要注意的是,children 只能爲一個元素,不能爲多個組件

屬性類型檢查

爲了保證組件傳遞屬性的正確性, 咱們能夠經過定義 propsType 對象來實現對組件屬性的嚴格校驗:

var MyComponent = React.createClass({
        propTypes: {
            optionalArray: React.PropTypes.array,
            optionalBool: React.PropTypes.bool,
            optionalFunc: React.PropTypes.func,
            optionalNumber: React.PropTypes.number,
            optionalObject: React.PropTypes.object,
            optionalString: React.PropTypes.string,
            // 任何能夠被渲染的包括,數字,字符串,組件,或者數組
            optionalNode: React.PropTypes.node,
            // React 元素
            optionalElement: React.PropTypes.element,
            // 枚舉
            optionalEnum: React.PropTypes.oneOf(['News', 'Photos']),
            // 任意一種類型
            optionalUnion: React.PropTypes.oneOfType([
              React.PropTypes.string,
              React.PropTypes.number,
              React.PropTypes.instanceOf(Message)
            ]),
            // 具體類型的數組
            optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number),
            // 具體類型的對象
            optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number),
            // 符合定義的對象
            optionalObjectWithShape: React.PropTypes.shape({
              color: React.PropTypes.string,
              fontSize: React.PropTypes.number
            }),
            requiredFunc: React.PropTypes.func.isRequired,
            requiredAny: React.PropTypes.any.isRequired,
            // 自定義校驗
            customProp: function(props, propName, componentName) {}
        }
    });

屬性傳遞的單向性

咱們已經提到過 React 的單向數據流模式,數據的流動管道就是 props,流動的方向就是組件的層級自定向下的方向。因此一個組件是不能修改自身的屬性的,組件的屬性必定是經過父組件傳遞而來(或者默認屬性)。

無狀態組件屬性

對於無狀態組件,能夠添加 .propTypes.defaultProps 屬性到函數上。

1.3.6 組件的嵌套組合

在 1.2 節的 JSX 實例子中,當咱們循環輸出 todo 列表的時候,React 會提示對於循環輸出的組件,須要有一個惟一的 key 屬性。這個問題的緣由在於 React 的調和機制(Reconciliation)上。

什麼叫調和?

在每次數據更新事後,React 會從新調用 render 渲染出新的組件結構,新的結構應用到 DOM 中的過程就叫作調和過程。

爲何須要調和?

想想,假設咱們有一個輸入組件,這個時候咱們正聚焦在輸入框中,當修改值事後觸發事件致使了數據改變,數據改變致使了重渲染, 這個時候輸入框被替換成了新的 DOM。 這個過程對用戶來講應該是無感知的,因此那原來的聚焦狀態應該被保存, 那怎麼作到的呢? DOM 都被替換了,輸入狀態,選擇狀態爲何還能保存。 咱們先不急着知道 How,目前只須要知道這就是調和過程,後面咱們會在 React 實現原理章節進行詳細介紹。

除了保存狀態之外,調和過程還作了不少 DOM 優化。 好比輸出一個數組的時候,數據新增長或者減小了一下,或者數組項值改變了,實際上咱們沒有必要刪除原來的 DOM 結構,只須要修改 DOM 的值或者刪除 DOM 就能實現重渲染。

這就是爲何要有 key 屬性,key 屬性可以幫助定位 DOM 與數組元素的關係,在重渲染的時候可以實現渲染優化。

1.3.7 實例練習:經過組件化的方式優化以前的待辦事項列表

問題

優化 JSX 語法練習的 TODOMVC 頁面, 經過組件化的方式拆分頁面!

組件以下:

  1. App 組件:整個頁面的最完成組件

  2. Header 組件:頭部輸入組件

  3. TodoList 組件:列表組件

  4. TodoItem 組件: 列表項

  5. Footer 組件:底部操做組件

Tips

循環輸出組件的方式

方式一:先計算出組件

function render() {
    var todos = this.props.todos;
    var $todos = todos.map(function(todo) {
        return <Todo data={todo}/>
    });
    return <div>
        {$todos}
    </div>
 }

方式二:{} 內直接計算

function render() {
    var todos = this.props.todos;
    return <div>
        {todos.map(function(todo) {
            return <Todo data={todo}/>
        })}
    </div>
 }

參考答案

https://github.com/leanklass/leanreact/tree/component

相關文章
相關標籤/搜索