書籍完整目錄html
在 React 中組件是第一元素,是 React 的基礎,一個 React 應用就是基於 React 組件的組合而成。
前面的 JSX 練習事後,你們應該對 React 組件不陌生了,在這一節咱們將溫習以及深刻學習 React 組件。node
建立一個 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
當建立好了組件事後,爲了將組件渲染到 DOM 中顯示出來,須要兩個步驟
在 HTML 中定義一個元素,設置 id 屬性
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 一次,將數據的變動放在組件內部,那麼就不會重複建立頂級組件。
React 中每一個組件能夠存儲本身的當前狀態, 以一個 switch 開關組件爲例,開關的狀態能夠存儲在組件內部。
React 的渲染結果是由組件屬性和狀態共同決定的,狀態和屬性的區別是,狀態維護在組件內部,屬性是由外部控制,咱們先介紹組件狀態相關細節:
控制狀態的 API 爲:
this.state:組件的當前狀態
getInitialState:獲取組件的初始狀態,在組件加載的時候會被調用一次,返回值賦予 this.state 做爲初始值
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 }); } })
前面已經提到過 React 組件能夠傳遞屬性給組件,傳遞方法和 HTML 中無異, 能夠經過 this.props 獲取組件屬性
屬性相關的 API 爲:
this.props: 獲取屬性值
getDefaultProps: 獲取默認屬性對象,會被調用一次,當組件類建立的時候就會被調用,返回值會被緩存起來,當組件被實例化事後若是傳入的屬性沒有值,會返回默認屬性值
this.props.children:子節點屬性
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 ,表示子組件, 仍是以上面一個組件爲例子,咱們能夠換一種方式定義 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.2 節的 JSX 實例子中,當咱們循環輸出 todo 列表的時候,React 會提示對於循環輸出的組件,須要有一個惟一的 key 屬性。這個問題的緣由在於 React 的調和機制(Reconciliation)上。
在每次數據更新事後,React 會從新調用 render 渲染出新的組件結構,新的結構應用到 DOM 中的過程就叫作調和過程。
想想,假設咱們有一個輸入組件,這個時候咱們正聚焦在輸入框中,當修改值事後觸發事件致使了數據改變,數據改變致使了重渲染, 這個時候輸入框被替換成了新的 DOM。 這個過程對用戶來講應該是無感知的,因此那原來的聚焦狀態應該被保存, 那怎麼作到的呢? DOM 都被替換了,輸入狀態,選擇狀態爲何還能保存。 咱們先不急着知道 How,目前只須要知道這就是調和過程,後面咱們會在 React 實現原理章節進行詳細介紹。
除了保存狀態之外,調和過程還作了不少 DOM 優化。 好比輸出一個數組的時候,數據新增長或者減小了一下,或者數組項值改變了,實際上咱們沒有必要刪除原來的 DOM 結構,只須要修改 DOM 的值或者刪除 DOM 就能實現重渲染。
這就是爲何要有 key 屬性,key 屬性可以幫助定位 DOM 與數組元素的關係,在重渲染的時候可以實現渲染優化。
優化 JSX 語法練習的 TODOMVC 頁面, 經過組件化的方式拆分頁面!
組件以下:
App 組件:整個頁面的最完成組件
Header 組件:頭部輸入組件
TodoList 組件:列表組件
TodoItem 組件: 列表項
Footer 組件:底部操做組件
循環輸出組件的方式
方式一:先計算出組件
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> }