函數(shù)式編程,對應(yīng)的是聲明式編程,聲明式編程的本質(zhì)的lambda驗算(是一個匿名函數(shù),即沒有函數(shù)名的函數(shù)。Lambda表達式可以表示閉包)
React元素
React元素使用JSON格式的代碼:
const DeleteAccount = () => ({
type: 'div',
props: {
children: [
{
type: 'p',
props: {
children: 'Are you sure?'
}
},
{
type: DangerButton,
props: {
children: 'Confirm'
}
},
{
type:Button,
props: {
children: 'Cancel'
}
}
]
}
})
React.createElement表示的代碼:
var DeleteAccount = function() {
return React.createElment(
'div',
null,
React.createElment(
'p',
null,
'Are you sure?'
),
React.createElment(
DangerButton,
null,
'Confirm',
),
React.createElment(
Button,
{ color: 'blue' },
'Cancel'
)
)
}
組件
組件封裝的時候,組件的幾項規(guī)范標準:
- 基本的封裝性
- 簡單的聲明周期呈現(xiàn)
- 明確的數(shù)據(jù)流動
Web Component 的4個組成部分:
- HTML Templates定義了模板的概念
- Custom Elements定義了組件的展現(xiàn)形式
- Shadow DOM定義了組件的作用域范圍,可以包括樣式
-
HTML Imports提出了新的引入方式
Web Components組成
React組件的構(gòu)建:
Web Component通過自定義元素的方式實現(xiàn)組件化,而React的本質(zhì)就是關(guān)心元素的構(gòu)成,React組件即為組件元素。組件元素被描述成純粹的JSON對象。
React組件基本上由3個部分組成 -- 屬性(props),狀態(tài)(state)以及生命周期

React所有組件繼承自頂層類 React.Component。它的定義非常簡潔,只是初始化了 React.Component方法,聲明了props、context、refs等,并在原型上定義了setState和forceUpdate方法。內(nèi)部初始化的聲明周期方法與createClass方法使用的是同一個方法創(chuàng)建的。這類組件會創(chuàng)建實例對象。
無狀態(tài)組件:使用無狀態(tài)函數(shù)構(gòu)建的組件,只傳入props和context兩個參數(shù),不存在state,沒有生命周期方法。它創(chuàng)建時始終保持了一個實例,避免了不必要的檢查和內(nèi)存分配,做到了內(nèi)部優(yōu)化
state
注意:setState是一個異步方法,一個生命周期內(nèi)所有的setState方法會合并操作。
智能組件(smart Component):內(nèi)部(State)更新數(shù)據(jù),控制組件渲染
木偶組件(dumb COmponent):外部(props)更新數(shù)據(jù),控制組件渲染
props
props是properties的縮寫。props是React用來組件之間互相聯(lián)系的一種機制,通俗的說就像方法的參數(shù)一樣。
子組件prop
在React中有一個重要且內(nèi)置的prop--children,它代表組件的子組件集合。組件props
在組件的prop中某一個屬性,我們傳入節(jié)點,然后渲染這個節(jié)點,和 children 類似用function prop與父組件通信
propTypes
用于規(guī)范props的類型與必須的狀態(tài)。
生命周期
分為兩類:
- 當組件在掛載或卸載時
- 當組件接收新的數(shù)據(jù)時,即組件更新時
組件的掛載,這個過程主要做組件狀態(tài)初始化
// 推薦使用這個例子作為模板寫初始化組件
class App extends Component {
// props類型檢查
static propTypes = {
// ...
};
// 默認類型
static defaultProps = {
// ...
};
constructor(props) { super(props);
this.state = {
// ...
};
}
componentWillMount() {
// ...
}
componentDidMount() {
// ...
}
render() {
return <div>This is a demo.</div>;
}
}
propTypes和defaultProps聲明成靜態(tài)屬性,意味著從類外面也可以訪問他們,比如:App.propTypes和App.defaultProps
組件的卸載
componentWillUnmount
這個方法中,我們常常會執(zhí)行一些清理方法,如事件回收或是清除定時器
數(shù)據(jù)更新過程
更新過程指父組件向下傳遞props或組件自身執(zhí)行setState方法時發(fā)生的一系列更新動作。
import React, { Component, PropTypes } from 'react';
class App extends Component {
componentWillReceiveProps(nextProps) {
// this.setState({})
}
shouldComponentUpdate(nextProps, nextState) {
// return true;
}
componentWillUpdate(nextProps, nextState) {
// ...
}
componentDidUpdate(prevProps, prevState) {
// ...
}
render() {
return <div>This is a demo.</div>;
}
}
如果state更新了,那么會依次執(zhí)行 shouldComponentUpdate、componentWillUpdate、render和componentDidUpdate
外來組件prop傳遞進來使組件更新
componentWillReceiveProps(nextProps) {
if ( 'activeIndex' in nextProps ) {
this.setState({
activeIndex: nextProps.activeIndex
})
}
}
整體流程


React與DOM
ReactDOM
ReactDOM中的API很少,只有findDOMNode、unmountComponentAtNode和render
- findDOMNode
DOM真正被添加到HTML中的生命周期方法是componentDidMount和componentDidUpdate方法。ReactDOM提供findDOMNode:DOMElement findDOMNode(ReactComponent component),假設(shè)當前組件加載完時獲取當前DOM,則可以使用findDOMNode
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
componentDidMount() {
// this 為當前組件的實例
const dom = ReactDOM.findDOMNode(this);
}
render() {}
}
- render
ReactComponent render(
ReactElement element,
DOMElement container,
[function callback]
)
該方法把元素掛在到container中,并返回element的實例(即refs引用)。如果是無狀態(tài)組件,render會返回null。組件掛載完畢時,callback就會被調(diào)用。
與 render 相反,React 還提供了一個很少使用的 unmountComponentAtNode 方法來進行
卸載操作
refs
ref -> reference
ref返回一個實例對象,也可以是一個回調(diào)函數(shù),focus的巧妙實現(xiàn),就是使用回調(diào)函數(shù)
import React, { Component } from 'react';
class App extends Component {
constructor(props){
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
if (this.myTextInput !== null) {
this.myTextInput.focus(); }
}
render() {
return (
<div>
<input type="text" ref={(ref) => this.myTextInput = ref} /> <input
type="button"
value="Focus the text input" onClick={this.handleClick}
/> </div>
);
React之外的DOM操作
DOM操作可以歸納為對DOM的增、刪、改、查?!安椤敝傅氖菍OM屬性、樣式的查看,比如DOM的位置、寬、高等信息。如果要調(diào)用HTML5 Audio/Video的play方法和input的focus方法,這時智能使用相應(yīng)的DOM方法來實現(xiàn)。
比如Popup等組件
componentDidUpdate(prevProps, prevState) {
if ( !this.state.isActive && prevState.isActive ) {
document.removeEventListener('click', this.hidePopup);
}
if( this.state.isActive && !prevState.isActive ) {
document.addEventListener('click', this.hidePopup)
}
}
componentWillUnmount() {
document.removeEventListener('click', this.hidePopup)
}
if( !this.isMounted() ) {
return false;
}
const node = ReactDOM.findDOMnode(this);
const target = e.target || e.srcElement;
const isInside = node.contains(target);
if ( this.state.isActive && !isInside ) {
this.setState({
isActive: false
})
}
計算DOM的尺寸(即位置信息),提供width和height這樣的工具函數(shù)
function width(el) {
const styles = el.ownerDocument.defaultView.getComputedStyle(el, null);
const width = parseFloat(styles.width.indexOf('px') !== -1 ? styles.width : 0);
const boxSizing = styles.boxSizing || 'content-box';
if (boxSizing === 'border-box') {
return width;
}
const borderLeftWidth = parseFloat(styles.borderLeftWidth);
const borderRightWidth = parseFloat(styles.borderRightWidth);
const paddingLeft = parseFloat(styles.paddingLeft);
const paddingRight = parseFloat(styles.paddingRight);
return width - borderRightWidth - borderLeftWidth - paddingLeft - paddingRight;
}
漫談React
事件系統(tǒng)
Virtual DOM在內(nèi)存中是以對象的形式存在的。React基于Virtual DOM實現(xiàn)了一個SyntheticEvent(合成事件)層,我們所定義的事件處理器會接收到一個SyntheticEvent對象的實例,它完全符合W3C標準。
所有事件自動綁定到最外層上,如需訪問原生事件對象,可以使員工nativeEvent屬性
合成事件實現(xiàn)機制
主要對合成事件做了兩件事:事件委派和自動綁定。
- 事件委派
事件代理機制。它并不會把事件處理函數(shù)直接綁定到真實的節(jié)點上,而是把所有事件綁定到結(jié)構(gòu)的最外層,使用一個統(tǒng)一的事件監(jiān)聽器,這個事件監(jiān)聽器上維持了一個映射來保存所有組件內(nèi)部的事件監(jiān)聽和處理函數(shù)。當組件掛載或卸載時,只是在這個統(tǒng)一的事件監(jiān)聽器上插入或刪除一些對象;;當事件發(fā)生時,首先被這個統(tǒng)一的事件監(jiān)聽器處理,然后在映射里找到真正的事件處理函數(shù)并調(diào)用。 - 自動綁定
每個方法的上下文都會指向該組件的實例,即自動綁定this為當前組件
只綁定,不傳參,可以使用一種便捷的方案--雙冒號語法,babel已經(jīng)支持
<button onClick={this.handleClick.bind(this)}></button>
<button onClick={::this.handleClick}></button>
// 箭頭函數(shù)。自動綁定定義此函數(shù)作用域的this,因此我們需要對它使用bind方法
const handleClick = (e) => {
console.log(e)
}
<button onClick={this.handleClick}></button>
// 或者
<button onClick={() => this.handleClick()}></button>
React中使用原生事件
componentDidMount會在組件已經(jīng)完成安裝并且在瀏覽器中存在真實的DOM后調(diào)用,因此我們就可以完成原生事件的綁定。
值得注意的是,在 React 中使用 DOM 原生事件時,一定要在組件卸載時手動移除,否則很可能出現(xiàn)內(nèi)存泄漏的問題。在原生事件中阻止冒泡行為,可以阻止React合成事件的傳播,阻止React事件冒泡的行為,不能阻止原生事件的冒泡。
對于無法使用React合成事件的場景,我們還需要使用原生事件來完成。
對比React合成事件與JavaScript原生事件
- 事件傳播與阻止事件傳播
DOM事件傳播三個階段:事件捕獲階段、目標對象本身的事件處理程序調(diào)用以及事件冒泡。
React只有事件冒泡 - 事件類型
- 事件綁定方式
原生事件綁定方式
1、直接在DOM元素綁定
<button onclick='alert(1)'></button>
2、JS中,通過為元素的事件屬性賦值的方式實現(xiàn)綁定
el.onClick = e => { }
3、通過事件監(jiān)聽函數(shù)實現(xiàn)綁定(addEventListener)
React只有一種
<button onClick={this.handleClick.bind(this)}></button>
4、事件對象
React不存在兼容性問題,DOM事件對象在W3C和IE標準中存在差異。
表單
受控組件
每當表單的狀態(tài)發(fā)生改變時,都會被寫入到組件的state中,這種組件在React中被稱為受控組件(controlled Component),其上綁定了一個change事件,表單的數(shù)據(jù)元素組件的state,并通過props傳入。
Reaact受控組件更新state的流程:
(1)可以通過在初始state中設(shè)置表單的默認值
(2)每當表單的值繁盛變化時,調(diào)用onChange事件處理器
(3)事件處理器通過合成事件對象e拿到改變后的狀態(tài),并更新應(yīng)用的state
(4)setState觸發(fā)視圖的重新渲染,完成表單組件值的更新
非受控組件
如果一個表單組件沒有value props(單選按鈕和復(fù)選框?qū)?yīng)的是checked prop)時,就可以稱為非受控組件。
受控組件和非受控組件的最大區(qū)別是:非受控組件的狀態(tài)并不會受應(yīng)用狀態(tài)的控制,應(yīng)用中也多了局部組件狀態(tài),而受控組件的值來自于組件的 state。
表單組件的幾個重要屬性
- 狀態(tài)屬性
- value:類型為text的input,textarea組件以及select組件
- checked:類型為radio,checkbox的組件借助boolean類型的selected
- selected:該屬性可作用于select組件下面的option上
樣式處理
基本樣式處理
- 自定義組件建議支持className prop,讓用戶使用時添加自定義樣式
- 設(shè)置行內(nèi)樣式時需要使用對象
使用 classnames庫來才做類。
CSS Modules
Vjeux拋出了React開發(fā)中遇到的一系列CSS相關(guān)問題,有以下幾點:
- 全局污染
- 命名混亂
- 依賴管理不徹底
- 無法共享變量
- 代碼壓縮不徹底
啟用CSS Modules
// webpack.config.js 的css-loader
css?modules&localIdentName=[name]__[local]-[hash:base64:5]
- 使用composes來組合樣式,進行樣式的復(fù)用
.base { /*所有通用樣式*/ }
.normal {
compose: base;
/* normal 其他樣式 */
}
- class命名技巧
BEM命名:
1、Block:對應(yīng)模塊名,如Dialog
2、Element:對應(yīng)模塊中的節(jié)點名Confirm Button
3、Modifier:對應(yīng)節(jié)點相關(guān)的狀態(tài),如disableed
組件中通信
父組件向子組件通信
通過props向子組件傳遞需要的信息子組件向父組件通信
1、利用回調(diào)函數(shù)
2、利用自定義事件機制
跨級組件通信
使用context
父組件中定義 ChildContext
// 父組件
static childContextTypes = {
color: PropTypes.string
}
getChildContext() {
return {
color: 'red'
}
}
// 子組件
static contextTypes = {
color: PropTypes.string
}
<p style={ { background: this.context.color } }></p>
組件間抽象
廣義中的mixin方法,就是用賦值的方式將mixin對象里的方法都掛載到原對象上,來實現(xiàn)對對象的混入。
ES6 Classes與decorator:
ES并沒有改變JavaScript面向?qū)ο蠓椒ɑ谠偷谋举|(zhì)。decorator是運用在運行時的方法。
高階組件
higher-order function(高階函數(shù))在函數(shù)式 編程中是一個基本的概念,它描述的是這樣一種函數(shù):這種函數(shù)接受函數(shù)作為輸入,或是輸出一 個函數(shù)。
高階組件(higher-order component),類似于高階函數(shù),它接受React 組件作為輸入,輸出一 個新的 React 組件。
實現(xiàn)高階組件的方法有如下兩種:
- 屬性代理(props proxy)。高階組件通過被包裹的React組件來操作props
- 反向繼承(inheritance inversion)。高階組件繼承于被包裹的React組件。
- 屬性代理
import React, { Componet } from 'react';
const MyContainer = (WrappedComponent) => {
class extends Component {
render() {
return <WrappedComponent {...this.props}/>
}
}
}
// 使用高階組件
class MyComponent extends Compnent {
// ...
}
export default MyContainer(MyComponent);
// 使用decorator
@MyContainer
class MyComponent extends Component {
render() {}
}
export default MyComponent;
上述執(zhí)行聲明周期的過程類似堆棧調(diào)用:
didmount -> HOC didmount -> (HOCs didmount) -> (HOCs will unmount) -> HOC will unmount -> unmount
高階組件的功能:
- 控制props
- 通過refs使用引用
import React, { Component } from 'react';
const MyContainer = (WrappedComponent) =>
class extends Component {
// 得到實例對象
proc(wrappedComponentInstance) {
// 執(zhí)行實例方法
wrappedComponentInstance.method()
}
render() {
const props = Object.assign({}, this.props, {
ref: this.proc.bind(this)
})
return <WrappedCompoent {...props} />
}
}
- 抽象State
- 使用其他元素包裹WrappedComponent
- 反向繼承
const MyContainer = (WrappedComponent) => {
class extends WrappedComponent {
render() {
return super.render()
}
}
}
他的HOC調(diào)用順序和隊列是一樣的:
didmount -> HOC didmount -> (HOCs didmount) -> will unmount -> HOC will didmount -> (HOCs will unmount)
它有兩個比較大的特點:
- 渲染劫持
渲染劫持指的就是高階組件可以控制 WrappedComponent 的渲染過程,并渲染各種各樣的結(jié)果。
// 渲染劫持的示例
const MyContainer = (WrappedComponent) =>
class extends WrappedComponet {
render() {
if ( this.props.loggedIn ) {
return super.render()
} else {
return null;
}
}
}
- 控制state
const MyContainer = (WrappedComponent) =>
class extends WrappedComponent {
render() {
return (
<div>
<h2>HOC Debugger Component</h2>
<p>Props</p> <pre>{JSON.stringify(this.props, null, 2)}</pre><p>State</p><pre>{JSON.stringify(this.state, null, 2)}</pre>
{super.render()}
</div>
);
}
}
- 組件命名
react-redux庫中已經(jīng)實現(xiàn)了HOC.displayName =HOC(${getDisplayName(WrappedComponent)})``
可以使用recompose庫
- 組件參數(shù)
import React, { Component } from 'react';
function HOCFactoryFactory(...param) {
// 可以做一些改變params的事
return function HOCFactory(WrappeComponent) {
return class HOC extend Component {
render() {
return <WrappedComponent {...this.props} />
}
}
}
}
// 使用
HOCFactoryFactory(params)(WrappedComponent)
@HOCFactoryFactory(params)
class WrappedComponent extends React.Component { }
組件疊加使用
多個組件方法,疊加使用,可以利用 compose方法包裹
const FinalSelector = compose(asyncSelectDecorator,searchDecorator, selectedItemDecorator)(Selector);

性能優(yōu)化
1、純函數(shù)
2、pureRender
官方在早期就為開發(fā)者提供了名為 react-addons-pure-render-mixin 的插件。其原理為重新實現(xiàn)了 shouldComponentUpdate生命周期方法,讓當前傳入的 props 和 state 與之前的作淺比較,如果返回 false,那么組件就不會執(zhí)行 render 方法。
3、Immutable
4、key
5、react-addons-perf
量化以上所做的性能優(yōu)化的效果。
動畫
React Transition
生命周期
- componentWillAppear
- componentDidAppear
- componentWillEnter
- componentDidEnter
- componentWillLeave
- componentDidLeave
componentWillxxx 在什么時候觸發(fā)很容易判斷,只需在componentWillReceiveProps 中對 this.props.children 和nextProps.children 做一個比較即可。而 componentDidxxx 要何時觸發(fā)呢?
可以給 componentWillxxx 提供一個回調(diào)函數(shù),用來執(zhí)行componentDidxxx。
緩動函數(shù)
自動化測試
Jest
