原教程內(nèi)容詳見精益 React 學(xué)習(xí)指南,這只是我在學(xué)習(xí)過程中的一些閱讀筆記硫麻,個人覺得該教程講解深入淺出,比目前大多數(shù)教程專業(yè)樊卓、系統(tǒng)拿愧。
1.1 Introduction to React
1.1.1 What is React?
React IS A JAVASCRIPT LIBRARY FOR BUILDING USER INTERFACES , INCLUDING
- React.js
- ReactRenders: ReactDOM / ReactServer / ReactCanvas
- Flux模式及其實現(xiàn)
- React Components
- React Native
- GraphQI + Relay
1.1.2 Basic concept in React
- React.js
React.js 是 React 的核心庫,在應(yīng)用中必須先加載核心庫
<script src="http://facebook.github.io/react/js/react.js"</script>
<script src="http://facebook.github.io/react/js/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
- ReactDOM.js
DOM渲染器碌尔,為了在 web 頁面中顯示開發(fā)的組件浇辜,需要調(diào)用 ReactDOM.render 方法, 第一個參數(shù)是 React 組件唾戚,第二個參數(shù)為 HTMLElement柳洋。 - JSX
JSX 是 React 自定義的語法,最終 JSX 會轉(zhuǎn)化為 JS 運行于頁面當(dāng)中 - Components
Component是 React 中的核心概念叹坦,頁面當(dāng)中的所有元素都是通過 React 組件來表達(dá)熊镣。 - Virtual DOM
React 抽象出來的虛擬 DOM 樹,虛擬樹是 React 高性能的關(guān)鍵募书。 - one-way reactive data flow
React 應(yīng)用的核心設(shè)計模式绪囱,數(shù)據(jù)流向自頂向下
1.1.3 Features of React
- Component的組合模式
組合模式有時候又叫做部分-整體模式,它使我們樹型結(jié)構(gòu)的問題中莹捡,模糊了簡單元素和復(fù)雜元素的概念鬼吵,客戶程序可以向處理簡單元素一樣來處理復(fù)雜元素,從而使得客戶程序與復(fù)雜元素的內(nèi)部結(jié)構(gòu)解耦。
React 就是基于組合模式篮赢, 無論是應(yīng)用等級還是一個表單亦或是一個按鈕都視為一個組件齿椅, 然后基于組件的組合構(gòu)建整個應(yīng)用琉挖,這樣的結(jié)構(gòu)一直是前端界想要卻遲遲不來的 web component。
基于組合模式的優(yōu)點:
- 構(gòu)建可重用的組件:組件的開發(fā)能夠形成公司的組件庫涣脚,每個業(yè)務(wù)的開發(fā)都能積累可重用的組件示辈。
- 無學(xué)習(xí)障礙:天然符合 HTML 的結(jié)構(gòu), 對前端開發(fā)者來說幾乎沒有學(xué)習(xí)障礙涩澡。
- 具有彈性的架構(gòu):組合模式很簡單卻很有效顽耳,能夠構(gòu)建簡單的頁面也能構(gòu)建大型的前端應(yīng)用。
- 源碼高可維護(hù)性:開發(fā)只是工作中的一部分妙同,應(yīng)用的上線才是噩夢的開始射富,很多大型應(yīng)用因為復(fù)制的業(yè)務(wù)邏輯導(dǎo)致無法快速響應(yīng)業(yè)務(wù)需求,可維護(hù)性低粥帚。
- One way data flow
Javascript 是腳本語言胰耗,不能像靜態(tài)語言一樣通過編譯定位為題,想要清晰的定位到應(yīng)用中的 bug 需要深入了解業(yè)務(wù)代碼芒涡,對于大型前端應(yīng)用來說柴灯,因為業(yè)務(wù)代碼量很大且復(fù)雜,很難定位到 bug费尽。 然而 React 的單向數(shù)據(jù)流的設(shè)計讓前端 bug 定位變得簡單赠群, 頁面的 UI 和數(shù)據(jù)的對應(yīng)是唯一的,我們可以通過定位數(shù)據(jù)變化就可以定位頁面展現(xiàn)問題。
- High efficiency
基于VirtualDOM算法可以讓只有需要改變的元素才去重渲染 - Separate frame design
React.js 現(xiàn)在的版本已經(jīng)將源碼分開為 ReactDOM 和 React.js . 這就意味著 React 不僅僅能夠在 web 端工作旱幼, 甚至可以在服務(wù)端(nodejs)查描,Native 端運行。
與此同時柏卤, 我們可以自定義自己的渲染器冬三, 實現(xiàn)比如 Three.js, Pixi.js缘缚, D3.js 的 React 方式渲染勾笆。
1.1.4 Where can React apply?
- web 端
- 原生應(yīng)用
IOS、Android桥滨、Native 應(yīng)用
這要歸功于 facebook 開源的 React Native窝爪。 基于 React Native , 我們將可以使用 jsx 來實現(xiàn)具有原生應(yīng)用性能的 UI 運行于 IOS 和 android 中齐媒,同時我們也可以通過 NW.js 或者 Electron 來實現(xiàn)基于 React 的桌面應(yīng)用酸舍。 - 服務(wù)器端渲染
React 除了在 Web 和 Native 環(huán)境以外, 也可以通過 React 實現(xiàn)在服務(wù)器端渲染出 HTML里初。
1.2 JSX Grammar
1.2.1 Prepare running environment
- Babel REPL
Babel REPL
直接在 REPL 中寫 JSX 語法啃勉,可以實時的查看編譯后的結(jié)果。 - JSFiddle
在線模式 React Fiddle - Local development
1.2.2 JSX Grammar
創(chuàng)建 JSX 語法的本質(zhì)目的是為了使用基于 xml 的方式表達(dá)組件的嵌套双妨,保持和 HTML 一致的結(jié)構(gòu)淮阐,語法上除了在描述組件上比較特別以外叮阅,其它和普通的 Javascript 沒有區(qū)別。 并且最終所有的 JSX 都會編譯為原生 Javascript泣特。
- jsx componments
JSX 組件分為 HTML 組件和 React 組件
HTML 組件就是 HTML 中的原生標(biāo)簽浩姥, 如:
function render() {
return <p> hello, React World </p>
}
function render() {
return <ul>
<li>list item 1</li>
<li>list item 2</li>
</ul>
}
React 組件就是自定義的組件,如
// 定義一個自定義組件
var CustomComponnet = React.createClass({
render: function() {
return <div> custom component </div>
}
});
// 使用自定義組件
function render() {
return <p> <CustomComponent/> </p>
}
- properties of jsx components
和 html 一樣状您,JSX 中組件也有屬性勒叠,傳遞屬性的方式也相同
對于 HTML 組件
function render() {
return <p title="title" >hello, React, world </p>
}
如果是 React 組件可以定義自定義屬性,傳遞自定義屬性的方式:
function render() {
return <p> <CustomComponent customProps="data"/> </p>
}
}
屬性即可以是字符串膏孟,也可以是任意的 Javascript 變量, 傳遞方式是將變量用花括號眯分, eg:
function render() {
var data = {a: 1, b:2};
return <p> <CustomComponent customProps={data}/> </p>
}
需要注意的地方上,屬性的寫法上和 HTML 存在區(qū)別柒桑,在寫 JSX 的時候弊决,所有的屬性都是駝峰式的寫法,主要是出于標(biāo)準(zhǔn)的原因魁淳,駝峰式是 Javascript 的標(biāo)準(zhǔn)寫法飘诗,并且 React 底層是將屬性直接對應(yīng)到原生 DOM 的屬性,而原生 DOM 的屬性是駝峰式的寫法界逛,這里也可以理解為什么類名不是 class 而是 className 了, 又因為 class 和 for 還是 js 關(guān)鍵字昆稿,所以 jsx 中:
class => className
for => htmlFor
除此之外比較特殊的地方是 data-*
和 aria-*
兩類屬性是和 HTML 一致的。
- curly braces
- 顯示文本
function render() {
var text = "Hello, World"
return <p> {text} </p>
}
- 運算
funtion render() {
var text = text;
var isTrue = false;
var arr = [1, 2, 3];
return <p>
{text}
{isTrue ? "true" : "false"}
{arr.map(function(it) {
return <span> {it} </span>
})}
</p>
}
- 限制規(guī)則
render 方法返回的組件必須是有且只有一個根組件息拜,錯誤情況的例子
// 無法編譯通過貌嫡,JSX 會提示編譯錯誤
function render() {
return (<p> .... </p>
<p> .... </p>)
}
- 組件命名空間
JSX 可以通過命名空間的方式使用組件, 通過命名空間的方式可以解決相同名稱不同用途組件沖突的問題。如:
function render() {
return <p>
<CustomComponent1.SubElement/>
<CustomComponent2.SubElement/>
</p>
}
1.2.3 Understand JSX
- JSX 的編譯方式
- 在 HTML 中引入 babel 編譯器, 如上 Hello World 程序中一樣该溯。
- 離線編譯 JSX,通過 babel 編譯 JSX别惦,細(xì)節(jié)我們將在第二章中講解狈茉。
- JSX 到 JS 的轉(zhuǎn)化
Hello World 程序轉(zhuǎn)化為 JS 的代碼如下:
var Hello = React.createClass({
displayName: 'Hello',
render: function() {
return React.createElement("div", null, "Hello ", this.props.name);
}
});
ReactDOM.render(
React.createElement(Hello, {name: "World"}),
document.getElementById('container')
);
1.3 React Components
1.3.1 Create a component
- 創(chuàng)建一個 React 組件的方法為,調(diào)用 React.createClass 方法掸掸,傳入的參數(shù)為一個對象氯庆,對象必須定義一個 render 方法,render 方法返回值為組件的渲染結(jié)構(gòu)扰付,也可以理解為一個組件實例(React.createElement 工廠方法的返回值)堤撵,返回值有且只能為一個組件實例,或者返回 null/false羽莺,當(dāng)返回值為 null/false 的時候实昨,React 內(nèi)部通過 <noscript/> 標(biāo)簽替換
var MyComponent = React.createClass({
render: function() {
return <p>....</p>;
}
});
- Component命名空間
可以看出 React.createClass 生成的組件類為一個 Javascript 對象。 當(dāng)我們想設(shè)置命名空間組件時盐固,可以在組件下面添加子組件
MyComponent.SubComponent = React.createClass({...});
MyComponent.SubComponent.Sub = React.createClass({....});
- 無狀態(tài)組件
除了可以通過 React.createClass 來創(chuàng)建組件以外荒给,組件也可以通過一個普通的函數(shù)定義丈挟,函數(shù)的返回值為組件渲染的結(jié)果
function StatelessComponent(props) {
return <div> Hello {props.name} </div>
}
無狀態(tài)組件能夠優(yōu)化性能,因為其內(nèi)部不會維護(hù)狀態(tài)志电,React 內(nèi)部不會有一個對應(yīng)的組件實例曙咽,并且也沒有生命周期 hook
1.3.2 將組件渲染到 DOM 中
當(dāng)創(chuàng)建好了組件過后,為了將組件渲染到 DOM 中顯示出來挑辆,需要兩個步驟:
- 在 HTML 中定義一個元素例朱,設(shè)置 id 屬性
- JSX 中調(diào)用 ReactDOM.render 方法, 第一個參數(shù)為 組件鱼蝉,第二個為剛才定義的 DOM 元素
<!-- 定義的 DOM 元素 -->
<div id="example"></div>
<script type="text/babel">
// 自定義組件
var MyComponent = React.createClass({
render: function() {
return <p>....</p>;
}
});
// 調(diào)用 render 方法
ReactDOM.render(
<MyComponent/>,
document.getElementById('example')
);
</script>
1.3.3 States of components
React 的渲染結(jié)果是由組件屬性和狀態(tài)共同決定的织咧,狀態(tài)和屬性的區(qū)別是伐割,狀態(tài)維護(hù)在組件內(nèi)部,屬性是由外部控制,我們先介紹組件狀態(tài)相關(guān)細(xì)節(jié):
控制狀態(tài)的 API 為:
-
this.state
:組件的當(dāng)前狀態(tài) -
getInitialState
:獲取組件的初始狀態(tài)看靠,在組件加載的時候會被調(diào)用一次,返回值賦予 this.state 作為初始值 -
this.setState
:組件狀態(tài)改變時稍味,可以通過 this.setState 修改狀- setState 方法支持按需修改隧熙,如 state 有兩個字段,僅當(dāng) setState 傳入的對象包含字段 key 才會修改屬性
- 每次調(diào)用 setState 會導(dǎo)致重渲染調(diào)用 render 方法
- 直接修改 state 不會重渲染組件
var Switch = React.createClass({
// 定義組件的初始狀態(tài)睬魂,初始為關(guān)
getInitialState: function() {
return {
open: false
}
},
// 通過 this.state 獲取當(dāng)前狀態(tài)
render: function() {
console.log('render switch component');
var open = this.state.open;
return <label className="switch">
<input type="checkbox" checked={open}/>
</label>
},
// 通過 setState 修改組件狀態(tài)
// setState 過后會 React 會調(diào)用 render 方法重渲染
toggleSwitch: function() {
var open = this.state.open;
this.setState({
open: !open
});
}
})
1.3.4 Properties of components
前面已經(jīng)提到過 React 組件可以傳遞屬性給組件终吼,傳遞方法和 HTML 中無異, 可以通過 this.props 獲取組件屬性
屬性相關(guān)的 API 為:
-
this.props
: 獲取屬性值 -
getDefaultProps
: 獲取默認(rèn)屬性對象,會被調(diào)用一次氯哮,當(dāng)組件類創(chuàng)建的時候就會被調(diào)用际跪,返回值會被緩存起來,當(dāng)組件被實例化過后如果傳入的屬性沒有值喉钢,會返回默認(rèn)屬性值 -
this.props.children
:子節(jié)點屬性 -
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'));
- 屬性類型檢查
為了保證組件傳遞屬性的正確性幔戏, 我們可以通過定義 propsType 對象來實現(xiàn)對組件屬性的嚴(yán)格校驗
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,
// 任何可以被渲染的包括,數(shù)字税课,字符串闲延,組件,或者數(shù)組
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)
]),
// 具體類型的數(shù)組
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) {}
}
});
- 屬性傳遞的單向性
我們已經(jīng)提到過 React 的單向數(shù)據(jù)流模式韩玩,數(shù)據(jù)的流動管道就是 props垒玲,流動的方向就是組件的層級自定向下的方向。所以一個組件是不能修改自身的屬性的找颓,組件的屬性一定是通過父組件傳遞而來(或者默認(rèn)屬性)合愈。 - 無狀態(tài)組件屬性
對于無狀態(tài)組件,可以添加 .propTypes 和 .defaultProps 屬性到函數(shù)上。
1.3.5 組件的嵌套組合
在JSX 實例子中想暗,當(dāng)我們循環(huán)輸出 todo 列表的時候妇汗,React 會提示對于循環(huán)輸出的組件,需要有一個唯一的 key 屬性说莫。這個問題的原因在于 React 的調(diào)和機制(Reconciliation)上杨箭。
- 什么叫調(diào)和?
在每次數(shù)據(jù)更新過后储狭,React 會重新調(diào)用 render 渲染出新的組件結(jié)構(gòu)互婿,新的結(jié)構(gòu)應(yīng)用到 DOM 中的過程就叫做調(diào)和過程。 - 為什么需要調(diào)和辽狈?
假設(shè)我們有一個輸入組件慈参,這個時候我們正聚焦在輸入框中,當(dāng)修改值過后觸發(fā)事件導(dǎo)致了數(shù)據(jù)改變刮萌,數(shù)據(jù)改變導(dǎo)致了重渲染驮配, 這個時候輸入框被替換成了新的 DOM。 這個過程對用戶來說應(yīng)該是無感知的着茸,所以那原來的聚焦?fàn)顟B(tài)應(yīng)該被保存壮锻, 那怎么做到的呢? DOM 都被替換了涮阔,輸入狀態(tài)猜绣,選擇狀態(tài)為什么還能保存。 我們先不急著知道 How敬特,目前只需要知道這就是調(diào)和過程掰邢。
除了保存狀態(tài)以外,調(diào)和過程還做了很多 DOM 優(yōu)化伟阔。 比如輸出一個數(shù)組的時候辣之,數(shù)據(jù)新增加或者減少了一下,或者數(shù)組項值改變了皱炉,實際上我們沒有必要刪除原來的 DOM 結(jié)構(gòu)怀估,只需要修改 DOM 的值或者刪除 DOM 就能實現(xiàn)重渲染。
這就是為什么要有 key 屬性娃承,key 屬性能夠幫助定位 DOM 與數(shù)組元素的關(guān)系,在重渲染的時候能夠?qū)崿F(xiàn)渲染優(yōu)化怕篷。
1.4 Life circle of React Components
14.1 Components
React 中組件有自己的生命周期方法历筝,簡單理解可以為組件從出生(實例化) -> 激活 -> 銷毀
生命周期 hook。通過這些 hook 方法可以自定義組件的特性廊谓。 除此之外梳猪,還可以設(shè)置一些額外的規(guī)格配置。
React.createClass
的參數(shù)對象中傳入, 之前使用過了一些方法:render getInitialState getDefaultProps propTypes
1.4.2 mixins
Type: array mixins
mixins 可以理解為 React 的插件列表春弥,通過這種模式在不同組件之間共享方法數(shù)據(jù)或者行為只需共享 mixin 就行呛哟,mixins 內(nèi)定義的生命周期方法在組件的生命周期內(nèi)都會被調(diào)用。
var MyMixin1 = {
componentDidMount: function() {
console.log('auto do something when component did mount');
}
};
var MyMixin2 = {
someMethod: function() {
console.log('doSomething');
}
};
var MyComponnet = React.createClass({
mixins: [MyMixin1, MyMixin2],
componentDidMount: function() {
// 調(diào)用 mixin1 共享的方法
this.someMethod();
}
});
1.4.3 statics
Type:object statics
statics可以定義組件的類方法
React 的組件是 面向?qū)ο驩OP 的思維匿沛,MyComponent 是一個 class扫责,class 分為類方法和實例方法,實例方法可以訪問 this, 然而類方法不能逃呼,所以我們不能在 Class 中返回狀態(tài)或者屬性鳖孤。
var MyComponent = React.createClass({
statics: {
customMethod: function(foo) {
return foo === 'bar';
}
}
});
MyComponent.customMethod('bar'); // true
1.4.4 displayName
Type: string displayName
為了顯示調(diào)試信息,每個組件都會有一個名稱抡笼,JSX 在轉(zhuǎn)為 JS 的時候自動的設(shè)置 displayName,當(dāng)然我們也可以自定義 displayName
// Input (JSX):
var MyComponent = React.createClass({ });
// Output (JS):
var MyComponent = React.createClass({displayName: "MyComponent", });
1.4.5 生命周期方法
1.4.6 componentWillMount
void componentWillMount()
- 條件:第一次渲染階段在調(diào)用 render 方法前會被調(diào)用
- 作用:該方法在整個組件生命周期只會被調(diào)用一次苏揣,所以可以利用該方法做一些組件內(nèi)部的初始化工作
1.4.7 componentDidMount
void componentDidMount()
- 條件:第一次渲染成功過后,組件對應(yīng)的 DOM 已經(jīng)添加到頁面后調(diào)用
- 作用:這個階段表示組件對應(yīng)的 DOM 已經(jīng)存在推姻,我們可以在這個時候做一些依賴 DOM 的操作或者其他的一些如請求數(shù)據(jù)平匈,和第三方庫整合的操作。如果嵌套了子組件增炭,子組件會比父組件優(yōu)先渲染弟跑,所以這個時候可以獲取子組件對應(yīng)的 DOM。
1.4.8 componentWillReceiveProps(newProps)
void componentWillReceiveProps(
object nextProps
)
- 條件: 當(dāng)組件獲取新屬性的時候防症,第一次渲染不會調(diào)用
- 用處: 這個時候可以根據(jù)新的屬性來修改組件狀態(tài)
componentWillReceiveProps: function(nextProps) {
this.setState({
likesIncreasing: nextProps.likeCount > this.props.likeCount
});
}
注意: 這個時候雖說是獲取新屬性蔫敲,但并不能確定屬性一定改變了饲嗽,例如一個組件被多次渲染到 DOM 中,如下面:
var Component = React.createClass({
componentWillReceiveProps: function(nextProps) {
console.log('componentWillReceiveProps', nextProps.data.bar);
},
rener: function() {
return <div> {this.props.data.bar} </div>
}
});
var container = document.getElementById('container');
var mydata = {bar: 'drinks'};
ReactDOM.render(<Component data={mydata} />, container);
ReactDOM.render(<Component data={mydata} />, container);
ReactDOM.render(<Component data={mydata} />, container);
結(jié)果會輸出兩次 componentWillReceiveProps,雖然屬性數(shù)據(jù)沒有改變尽狠,但是仍然會調(diào)用 componentWillReceiveProps 方法袄膏。
1.4.9 shouldComponentUpdate(nextProps, nextState)
boolean shouldComponentUpdate(
object nextProps, object nextState
)
- 條件: 接收到新屬性或者新狀態(tài)的時候在 render 前會被調(diào)用(除了調(diào)用 forceUpdate 和初始化渲染以外)
- 用處: 該方法讓我們有機會決定是否重渲染組件沉馆,如果返回 false斥黑,那么不會重渲染組件锌奴,借此可以優(yōu)化應(yīng)用性能(在組件很多的情況)缨叫。
1.4.10 componentWillUpdate
void componentWillUpdate(
object nextProps, object nextState
)
- 條件:當(dāng)組件確定要更新销钝,在 render 之前調(diào)用
- 用處:這個時候可以確定一定會更新組件蒸健,可以執(zhí)行更新前的操作
- 注意:方法中不能使用 setState 似忧,setState 的操作應(yīng)該在 componentWillReceiveProps 方法中調(diào)用
1.4.11 componentDidUpdate
void componentDidUpdate(
object prevProps, object prevState
)
- 條件:更新被應(yīng)用到 DOM 之后
- 用處:可以執(zhí)行組件更新過后的操作
1.4.12 生命周期與單向數(shù)據(jù)流
我們知道 React 的核心模式是單向數(shù)據(jù)流盯捌,這不僅僅是對于組件級別的模式,在組件內(nèi)部 的生命周期中也是應(yīng)該符合單向數(shù)據(jù)的模式幼衰。數(shù)據(jù)從組件的屬性流入渡嚣,再結(jié)合組件的狀態(tài)识椰,流入生命周期方法腹鹉,直到渲染結(jié)束這都應(yīng)該是一個單向的過程,其間不能隨意改變組件的狀態(tài)航瞭。1.5 React & DOM
1.5.1獲取DOM元素
DOM真正被添加到HTML中的hook為
- componentDidMount
- componentDidUpdate
在這兩個 hook 函數(shù)中, 我們可以獲取真正的 DOM 元素滨彻,React 提供的獲取方法兩種方式
- findDOMNode()
通過 ReactDOM 提供的 findDOMNode 方法亭饵, 傳入?yún)?shù)
var MyComponent = React.createClass({
render: function() {
return <div> .... </div>
},
componentDidMount: function() {
var $root = ReactDOM.findDOMNode(this);
console.log($root);
}
})
需要注意的是此方法不能應(yīng)用到無狀態(tài)組件上
- Refs
上面的方法只能獲取到 root 元素辜羊,那如果我的 DOM 有很多層級,我想獲取一個子級的元素呢昔驱?React 提供了 ref 屬性來實現(xiàn)這種需求骤肛。
每個組件實例都有一個 this.refs 屬性萌衬,會自動引用所有包含 ref 屬性組件的 DOM
var MyComponent = React.createClass({
render: function() {
return <div>
<button ref="btn">...</button>
<a href="" ref="link"></a>
</div>
},
componentDidMount: function() {
var $btn = this.refs.btn;
var $link = this.refs.link;
console.log($btn, $link);
}
})
1.5.2 DOM事件
- 綁定事件
在 React 中綁定事件的方式很簡單,只需要在元素中添加事件名稱的屬性已經(jīng)對應(yīng)的處理函數(shù)混移,如:
var MyComponent = React.creatClass({
render: function() {
return <div>
<button onClick={this.onClick}>Click Me</button>
</div>
},
onClick: function() {
console.log('click me');
}
});
事件名稱和其他屬性名稱一樣,服從駝峰式命名回铛。
- 合成事件(SyntheticEvent)
在 React 中茵肃, 事件的處理由其內(nèi)部自己實現(xiàn)的事件系統(tǒng)完成捞附,觸發(fā)的事件都叫做 合成事件(SyntheticEvent)鸟召,事件系統(tǒng)對瀏覽器做了兼容欧募,其提供的 API 與原生的事件無異槽片。
boolean bubbles
boolean cancelable
DOMEventTarget currentTarget
boolean defaultPrevented
number eventPhase
boolean isTrusted
DOMEvent nativeEvent
void preventDefault()
boolean isDefaultPrevented()
void stopPropagation()
boolean isPropagationStopped()
DOMEventTarget target
number timeStamp
string type
和原生事件的區(qū)別在于,事件不能異步話剩盒,如:
function onClick(event) {
console.log(event); // => nullified object.
console.log(event.type); // => "click"
var eventType = event.type; // => "click"
setTimeout(function() {
console.log(event.type); // => null
console.log(eventType); // => "click"
}, 0);
this.setState({clickEvent: event}); // Won't work. this.state.clickEvent will only contain null values.
this.setState({eventType: event.type}); // You can still export event properties.
}
原因是在事件系統(tǒng)的內(nèi)部實現(xiàn)當(dāng)中, 一個事件對象可能會被重用(也就是事件做了池化 Pooling)跟匆。當(dāng)一個事件響應(yīng)函數(shù)執(zhí)行過后,事件的屬性被設(shè)置為 null迹冤, 如果想用保持事件的值的話泡徙,可以調(diào)用
event.persist()
這樣堪藐,屬性會被保留贮勃,并且事件也會被從池中取出。
- 事件捕獲和冒泡
在 DOM2.0 事件分為捕獲階段和冒泡階段枫绅,React 中通常我們注冊的事件為冒泡事件,如果要注冊捕獲階段的事件县耽,可以在事件名稱后加 Capture 如:
onClick
onClickCapture
- 支持事件列表
粘貼板事件 {
事件名稱:onCopy onCut onPaste
屬性:DOMDataTransfer clipboardData
}
編輯事件 {
事件名稱:onCompositionEnd onCompositionStart onCompositionUpdate
屬性:string data
}
鍵盤事件 {
事件名稱:onKeyDown onKeyPress onKeyUp
屬性: {
boolean altKey
number charCode
boolean ctrlKey
boolean getModifierState(key)
string key
number keyCode
string locale
number location
boolean metaKey
boolean repeat
boolean shiftKey
number which
}
}
// 焦點事件除了表單元素以外,可以應(yīng)用到所有元素中
焦點事件 {
事件名稱:onFocus onBlur
屬性:DOMEventTarget relatedTarget
}
表單事件 {
事件名稱:onChange onInput onSubmit
}
鼠標(biāo)事件 {
事件名稱:{
onClick onContextMenu onDoubleClick onDrag onDragEnd onDragEnter onDragExit onDragLeave onDragOver onDragStart onDrop onMouseDown onMouseEnter onMouseLeave onMouseMove onMouseOut onMouseOver onMouseUp
}
屬性:{
boolean altKey
number button
number buttons
number clientX
number clientY
boolean ctrlKey
boolean getModifierState(key)
boolean metaKey
number pageX
number pageY
DOMEventTarget relatedTarget
number screenX
number screenY
boolean shiftKey
}
}
選擇事件 {
事件名稱:onSelect
}
觸摸事件 {
事件名稱:onTouchCancel onTouchEnd onTouchMove onTouchStart
屬性:{
boolean altKey
DOMTouchList changedTouches
boolean ctrlKey
boolean getModifierState(key)
boolean metaKey
boolean shiftKey
DOMTouchList targetTouches
DOMTouchList touches
}
}
UI 事件 {
事件名稱:onScroll
屬性:{
number detail
DOMAbstractView view
}
}
滾輪事件 {
事件名稱:onWheel
屬性:{
number deltaMode
number deltaX
number deltaY
number deltaZ
}
}
媒體事件 {
事件名稱:{
onAbort onCanPlay onCanPlayThrough onDurationChange onEmptied onEncrypted onEnded onError onLoadedData onLoadedMetadata onLoadStart onPause onPlay onPlaying onProgress onRateChange onSeeked onSeeking onStalled onSuspend onTimeUpdate onVolumeChange onWaiting
}
}
圖像事件 {
事件名稱:onLoad onError
}
動畫事件 {
事件名稱:onAnimationStart onAnimationEnd onAnimationIteration
屬性:{
string animationName
string pseudoElement
float elapsedTime
}
}
漸變事件 {
事件名稱:onTransitionEnd
屬性: {
string propertyName
string pseudoElement
float elapsedTime
}
}
1.5.3 表單事件
在 React 中比較特殊的事件是表單事件赶舆,大多數(shù)組件都是通過屬性和狀態(tài)來決定的芜茵,但是表單組件如 input, select, option 這些組件的狀態(tài)用戶可以修改绞佩,在 React 中會特殊處理這些組件的事件征炼。
- onChange 事件
和普通 HTML 中的 onChange 事件不同谆奥, 在原生組件中宰译,只有 input 元素失去焦點才會觸發(fā) onChange 事件沿侈, 在 React 中,只要元素的值被修改就會觸發(fā) onChange 事件蛛淋。
var MyComponent = React.createClass({
getInitialState: function() {
return {
value: ''
}
},
render: function() {
return <div onChange={this.onChangeBubble}>
<input value={this.state.value} onChange={this.onChange}/>
</div>
},
onChange: function(ev) {
console.log('change: ' + ev.target.value);
this.setState({
value: ev.target.value
});
},
// onChange 事件支持所有組件,可以被用于監(jiān)聽冒泡事件
onChangeBubble: function(ev) {
console.log('bubble onChange event', + ev.target.value);
}
})
- 交互屬性
表單組件中能被用戶修改的屬性叫交互屬性叛甫,包括:
value => <input> 和 <select> 組件
checked => <input type="checkbox|radio">
selected => <opiton>
- textarea
在 HTML 中,textarea 的值是像如下定義的:
<textarea name="" id="" cols="30" rows="10">
some value
</textarea>
而在 React 中棠赛, TextArea 的使用方式同 input 組件睛约,使用 value 來設(shè)置值
var MyComponent = function() {
render: function() {
return <div>
<textarea value={...} onChange={...}/>
</div>
}
}
- select 組件
在 React 中 select 組件支持 value 值,value 值還支持多選
<select value="B">
<option value="A">Apple</option>
<option value="B">Banana</option>
<option value="C">Cranberry</option>
</select>
<select multiple={true} value={['B', 'C']}>
<option value="A">Apple</option>
<option value="B">Banana</option>
<option value="C">Cranberry</option>
</select>
- 受控組件
在 React 中表單組件可分為兩類,受控與非受控組件商膊,受控組件是包含了 value 值的晕拆,如:
render: function() {
return <input type="text" value="....."/>
}
為什么叫受控組件吝镣? 因為這個時候用戶不能修改 input 的值末贾, input 的值永遠(yuǎn)是 value 固定了的值。如果去掉 value 屬性拴测,那么就可以輸入值了寸齐。
那如何修改受控組件的值呢渺鹦? 如上面的例子中塞颁, 添加 onChange 事件祠锣,事件內(nèi)修改 value 屬性,value 屬性的值會被設(shè)置到組件的 value 中澡腾。
- 非受控組件
沒有 value 值的 input
render: function() {
return <input type="text"/>
}
可以通過 defaultValue 屬性來設(shè)置默認(rèn)值
render: function() {
return <input type="text" defaultValue="Default Value">
}
類似的對于 checkbox 有 defaultChecked 屬性
需要注意的是,默認(rèn)值只適用于第一次渲染澜公,在重渲染階段將不會適用蜕青。
- checkbox & radio
checkbox 和 radio 比較特殊, 如果在 onChange 事件中調(diào)用了 preventDefault 贺喝,那么瀏覽器不會更新 checked 狀態(tài),即便事實上組件的值已經(jīng) checked 或者 unchecked 了 染苛。
var CheckBox = React.createClass({
getInitialState: function(){
return {
checked: false
}
},
render: function() {
return <div>
<input type="checkbox"
checked={this.state.checked}
onChange={this.onChange}/>
</div>
},
onChange: function(ev) {
this.setState({
checked: true
});
ev.preventDefault();
}
})
這個例子里邊,checked 雖然更新為 true 畔师,但是 input 的值 checked 為 false
那應(yīng)如何處理 checkbox 呢?
- 避免調(diào)用 ev.preventDefault
- 在 setTimeout 中處理 checked 的修改
- 使用 click 事件
1.5.4 Style屬性
在 React 中伯铣,可以直接設(shè)置 style 屬性來控制樣式,不過與 HTML 不同的是蹬蚁, 傳入的 style 值為一個object, 對象的所有 key 都是駝峰式命名叽粹,eg:
render: function() {
var style = {
backgroundColor: 'red',
height: 100,
width: 100
}
return <div style={style}></div>
}
其中還可以看到不同的地方時,為了簡寫寬度高度值虫几,可以直接設(shè)置數(shù)字锤灿,對應(yīng) 100 -> 100px
。如果某些屬性不需要添加 px 后綴辆脸,React 也會自動去除但校。
通過屬性值駝峰式的原因是 DOM 內(nèi)部訪問 style 也是駝峰式。如果需要添加瀏覽器前綴瑞 -webkit-状囱、-ms- 大駝峰(除了 ms ), 如:
var divStyle = {
WebkitTransition: 'all', // 'W' 是大寫
msTransition: 'all' // 'ms' 為小寫
};
在以前的前端開發(fā)方式是 樣式結(jié)構(gòu)和邏輯要分離, 而現(xiàn)在 React 中卻有很多人推崇** inline **的樣式倘是。 在我看來因人而異亭枷,React 的這種模式也能做到樣式模塊化,樣式重用(借用 Js 的特點)搀崭。并且因為 React 的實現(xiàn)方式叨粘,Inline 樣式的性能甚至比 class 的方式高。
1.6 Flux
1.6.1 Flux 介紹
簡單來講瘤睹,F(xiàn)lux 是 Facebook 引入到 React 中的一種前端架構(gòu)升敲,通過定義其核心單向數(shù)據(jù)流的方式,讓 React 應(yīng)用更加健壯默蚌。同時冻晤,這種應(yīng)用架構(gòu)也具有普適性苇羡,可以應(yīng)用到其他任意前端項目中绸吸,甚至可以應(yīng)用到客戶端應(yīng)用開發(fā)中,也就是說 Flux 更應(yīng)該叫做一種架構(gòu)模式(Pattern)设江。
1.6.2 MVC 架構(gòu)之痛
MVC 的實現(xiàn)可能有很多種方式锦茁,比較靈活,但基本本質(zhì)不會改變叉存,只是三者間的數(shù)據(jù)傳遞方向可能會改變码俩,即便是 MVP 模式也只是 MVC 的變種,所以為了統(tǒng)一我們且以下圖的 MVC 方式來討論歼捏。
- 概念
- Model: 負(fù)責(zé)保存應(yīng)用數(shù)據(jù)稿存,和后端交互同步應(yīng)用數(shù)據(jù)
- View: 負(fù)責(zé)渲染頁面 HTML DOM
- Controller: 負(fù)責(zé)連接 View 和 Model , Model 的任何改變會應(yīng)用到 View 中瞳秽,View 的操作會通過 Controller 應(yīng)用到 Model 中
- 關(guān)系:Model, View, Controller 都是多對多關(guān)系瓣履。
- 流程
以 TODOMVC 為例子用戶添加一個 todo 的交互流程:
View -> Action -> Controller -> Model -> View
- View -> Action: 添加按鈕事件或者 input 輸入的提交事件
- Action -> Controller: 控制器響應(yīng) View 事件
- Controller -> Model: 控制器依賴 Model, 調(diào)用 Model 添加 todo
- Model -> View: View 監(jiān)聽 Model 的改變添加 todo 事件,在 HTML 中添加一個新的 Todo 視圖
- 問題
對于新增一個 todo 练俐,需要編寫一個視圖渲染處理函數(shù)袖迎,函數(shù)內(nèi)添加新項目到列表中。同理對于刪除一個 todo,也會有一個處理函數(shù)燕锥。當(dāng)業(yè)務(wù)邏輯變多過后辜贵,可能有很多模型需要做增刪改的功能,與之對應(yīng)的就是我們需要精心構(gòu)建這么多的渲染處理函數(shù)归形。 這種局部更新模式是高性能的關(guān)鍵所在托慨,但問題是:
- 更新邏輯復(fù)雜,需要編寫大量的局部渲染函數(shù)
- 問題定位困難连霉,頁面的當(dāng)前狀態(tài)是有數(shù)據(jù)和這些局部更新函數(shù)確定的
- 如何解決
如果渲染函數(shù)只有一個榴芳,統(tǒng)一放在 App 控制器中,每次更新重渲染頁面跺撼,這樣的話:- 任何數(shù)據(jù)的更新都只用調(diào)用重渲染就行
- 數(shù)據(jù)和當(dāng)前頁面的狀態(tài)是唯一確定的
重渲染也有弊端窟感,會帶來嚴(yán)重的性能問題,重渲染和局部渲染各有好壞歉井,對 MVC 來說這是一個兩難的選擇柿祈,無法做到魚和熊掌兼得。
1.6.3 Flux 架構(gòu)
通過 React + Flux 就可以完美解決 MVC 的問題哩至。簡單來說在 Flux 架構(gòu)中直接剝離了控制器層躏嚎,MVC 架構(gòu)變成了 MV + Flux 架構(gòu)。
- 重渲染: 在 React 中每次渲染都是重渲染菩貌,且不影響頁面性能卢佣,是因為重渲染的是 Virtual Dom。這就意味著完全不用去關(guān)系重渲染問題箭阶,增刪改的渲染都和初始化渲染相同入口
- 數(shù)據(jù)和狀態(tài)一致性: Store 的數(shù)據(jù)確定應(yīng)用唯一的狀態(tài)
- 概念
-
one way data flow
這是 Flux 架構(gòu)的核心思想虚茶,從圖中可以看到,數(shù)據(jù)的流向從action 到 view 的一個單向流仇参。
-
Action
Action 可以理解為對應(yīng)用數(shù)據(jù)修改的指令嘹叫,任何修改應(yīng)用數(shù)據(jù)的行為都必須需通過觸發(fā) action 來修改。Action 可以來自于 View诈乒,也可以來自服務(wù)端的數(shù)據(jù)更新罩扇。
-
Action creator
為了抽象 Action ,提供一些輔助的語義化的方法來創(chuàng)建 Action怕磨,這些輔助方法叫做 Action Creator喂饥。
- Stores
應(yīng)用的數(shù)據(jù)中心,所有應(yīng)用數(shù)據(jù)都存放在這里控制肠鲫,同時包含數(shù)據(jù)的控制行為员帮,可能包含多個 store. - Dispatcher
action 的控制者,所有 action 都會通過 dispatcher滩届,由 dispatcher 控制 action 是否應(yīng)該傳入到 store 中集侯,Dispatcher 是一個單例被啼。 - View
頁面的視圖,對應(yīng) React 的 Component, 視圖可以觸發(fā) action 到 dispatcher棠枉。
需要區(qū)別出一種叫控制器 View(Controller View)的類型浓体,這種 View 可以知曉 store 數(shù)據(jù),把 store 數(shù)據(jù)轉(zhuǎn)化為自身的狀態(tài)辈讶,在將數(shù)據(jù)傳遞給其他 view 命浴。 并且可以監(jiān)聽 store 數(shù)據(jù)的改變,當(dāng) store 數(shù)據(jù)改變過后重新設(shè)置狀態(tài)觸發(fā)重渲染贱除。 可以將控制器 View 對應(yīng) MVC 中的控制器生闲,但是差別很大,控制器 View 唯一多做的事情就是監(jiān)聽 store 數(shù)據(jù)改變月幌,沒有其他任何業(yè)務(wù)處理邏輯碍讯。
- 流程
同樣以 TODOMVC 的添加 todo 為例,F(xiàn)lux 中的流程為:
View -> Action(Action Creator -> Action) -> Dispatcher -> Store -> Controller View -> View
- View -> Action: 添加按鈕事件或者 input 輸入的提交事件扯躺,View 中將事件轉(zhuǎn)化為 action, action 由 Action Creator 創(chuàng)建捉兴。
- Action -> Dispatcher: action 統(tǒng)一由 Dispatcher 分配
- Dispatcher -> Store: Dispatcher 分配 action 到 Store
- Store -> Controller View: 控制器 View 監(jiān)聽 store 的數(shù)據(jù)改變,將數(shù)據(jù)轉(zhuǎn)化為自身屬性
- Controller View -> View: 數(shù)據(jù)改變自動重渲染所有視圖
與MVC的對比 - 渲染策略: 數(shù)據(jù)改變 Flux 自動渲染录语,MVC 手動編寫更新函數(shù)
- 事件觸發(fā)策略: Flux 中所有 action 交給 dispather 分配倍啥,MVC 中交給對應(yīng)的控制器分配
- Flux 在核心策略上的不同是解決 MVC 架構(gòu)問題的關(guān)鍵
1.6.4 理解 Flux 架構(gòu)
Flux 架構(gòu)是非常優(yōu)雅簡潔的,合理利用了一些優(yōu)秀的架構(gòu)思維
- 分而治之(Divide And Conquer)
數(shù)據(jù)的處理過程是 Store -> Controller View -> View澎埠。 所有數(shù)據(jù)來自于 Store虽缕,頁面的渲染層級為 Store 將數(shù)據(jù)傳入 Controller View, 再由 Controller View 傳入子 View , 一直到 View 的葉子節(jié)點。
這個是一個典型的分而治之策略蒲稳,將大的頁面拆分為小的模塊氮趋,再由小的模塊拆分為小的組件,具體組件負(fù)者組件自身的問題弟塞,所有子組件都是自私的凭峡,不用關(guān)心“大家”拙已,只用關(guān)心“小家”决记。 - 合而治之 - 中心化控制
Flux 把所有的 View 都視作愚民,Store 視作資源的擁有者為統(tǒng)治者倍踪,統(tǒng)治者需要提供資源(數(shù)據(jù))給平民系宫,但是如果平民企圖對資源修改(Mutation),必須得先通知給統(tǒng)治者建车,讓統(tǒng)治者決定是否做處理扩借。
我們?yōu)?Flux 中的概念分配角色
View
: 平民
Action
: 資源修改操作
Dispatcher
: 審核官
Store
: 統(tǒng)治者
一個企圖修改資源的操作可以描述為:
View Require Mutation -> Action -> Dispatcher -> Store -> Mutate Handler
平民提交 Mutation 請求,由審核官控制缤至,審核通過后遞交給統(tǒng)治者潮罪,統(tǒng)治者再分配給親信做資源 Mutation
合而治之的策略也等于中心化控制策略, 作為統(tǒng)治者既要懂得放權(quán)利(資源的分配),也要懂得控制權(quán)利(資源的修改)嫉到,這種收縮自如的合理性是 Flux 簡潔的根本沃暗。
同時這種思維帶來的優(yōu)點如下:
- View 的獨立性和簡單性:View 自身的邏輯簡單,不需要知道太多事情何恶,只關(guān)心上級傳來的數(shù)據(jù)孽锥,這種模式使得 View 是低耦合的,簡潔的细层。
- 高可維護(hù)性:中心化控制知道所有對資源的操作惜辑,如果發(fā)生 bug, 可以很快定位問題
- 函數(shù)式編程思想
在 Flux 中數(shù)據(jù)的單向流動依賴于 View 的確定性,相同的數(shù)據(jù)傳入相同的組件疫赎,得到的結(jié)果必然要相同盛撑,這是函數(shù)式編程的思想。
為了保證組件也能做到 “純函數(shù)” 的特性捧搞,相同的屬性會得到相同的渲染結(jié)果撵彻。 在寫 React 組件的時候盡量準(zhǔn)守一下約定:
1.盡量使用無狀態(tài)組件
2.除了控制類組件以外其他組件避免使用組件狀態(tài)
3.可以通過屬性計算出來的狀態(tài)不要用狀態(tài)來表示
4.組件的渲染避免外部依賴,按照純函數(shù)的方式寫
函數(shù)式的優(yōu)點也是無副作用組件的優(yōu)點:
- 無耦合实牡,可移植性強: 組件可重用性高
- 可測試性高:組件無依賴陌僵,可以很容易的單獨測試組件