函數(shù)式編程租冠,對(duì)應(yīng)的是聲明式編程,聲明式編程的本質(zhì)的lambda驗(yàn)算(是一個(gè)匿名函數(shù)政敢,即沒(méi)有函數(shù)名的函數(shù)。Lambda表達(dá)式可以表示閉包)
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'
)
)
}
組件
組件封裝的時(shí)候唯灵,組件的幾項(xiàng)規(guī)范標(biāo)準(zhǔn):
- 基本的封裝性
- 簡(jiǎn)單的聲明周期呈現(xiàn)
- 明確的數(shù)據(jù)流動(dòng)
Web Component 的4個(gè)組成部分:
- HTML Templates定義了模板的概念
- Custom Elements定義了組件的展現(xiàn)形式
- Shadow DOM定義了組件的作用域范圍,可以包括樣式
-
HTML Imports提出了新的引入方式
React組件的構(gòu)建:
Web Component通過(guò)自定義元素的方式實(shí)現(xiàn)組件化隙轻,而React的本質(zhì)就是關(guān)心元素的構(gòu)成埠帕,React組件即為組件元素。組件元素被描述成純粹的JSON對(duì)象玖绿。
React組件基本上由3個(gè)部分組成 -- 屬性(props)敛瓷,狀態(tài)(state)以及生命周期
React所有組件繼承自頂層類(lèi) React.Component。它的定義非常簡(jiǎn)潔斑匪,只是初始化了 React.Component方法呐籽,聲明了props、context、refs等绝淡,并在原型上定義了setState和forceUpdate方法宙刘。內(nèi)部初始化的聲明周期方法與createClass方法使用的是同一個(gè)方法創(chuàng)建的苍姜。這類(lèi)組件會(huì)創(chuàng)建實(shí)例對(duì)象牢酵。
無(wú)狀態(tài)組件:使用無(wú)狀態(tài)函數(shù)構(gòu)建的組件,只傳入props和context兩個(gè)參數(shù)衙猪,不存在state馍乙,沒(méi)有生命周期方法。它創(chuàng)建時(shí)始終保持了一個(gè)實(shí)例垫释,避免了不必要的檢查和內(nèi)存分配丝格,做到了內(nèi)部?jī)?yōu)化
state
注意:setState是一個(gè)異步方法,一個(gè)生命周期內(nèi)所有的setState方法會(huì)合并操作棵譬。
智能組件(smart Component):內(nèi)部(State)更新數(shù)據(jù)显蝌,控制組件渲染
木偶組件(dumb COmponent):外部(props)更新數(shù)據(jù),控制組件渲染
props
props是properties的縮寫(xiě)订咸。props是React用來(lái)組件之間互相聯(lián)系的一種機(jī)制曼尊,通俗的說(shuō)就像方法的參數(shù)一樣。
子組件prop
在React中有一個(gè)重要且內(nèi)置的prop--children脏嚷,它代表組件的子組件集合骆撇。組件props
在組件的prop中某一個(gè)屬性,我們傳入節(jié)點(diǎn)父叙,然后渲染這個(gè)節(jié)點(diǎn)神郊,和 children 類(lèi)似用function prop與父組件通信
propTypes
用于規(guī)范props的類(lèi)型與必須的狀態(tài)。
生命周期
分為兩類(lèi):
- 當(dāng)組件在掛載或卸載時(shí)
- 當(dāng)組件接收新的數(shù)據(jù)時(shí)趾唱,即組件更新時(shí)
組件的掛載涌乳,這個(gè)過(guò)程主要做組件狀態(tài)初始化
// 推薦使用這個(gè)例子作為模板寫(xiě)初始化組件
class App extends Component {
// props類(lèi)型檢查
static propTypes = {
// ...
};
// 默認(rèn)類(lèi)型
static defaultProps = {
// ...
};
constructor(props) { super(props);
this.state = {
// ...
};
}
componentWillMount() {
// ...
}
componentDidMount() {
// ...
}
render() {
return <div>This is a demo.</div>;
}
}
propTypes和defaultProps聲明成靜態(tài)屬性,意味著從類(lèi)外面也可以訪(fǎng)問(wèn)他們甜癞,比如:App.propTypes和App.defaultProps
組件的卸載
componentWillUnmount
這個(gè)方法中夕晓,我們常常會(huì)執(zhí)行一些清理方法,如事件回收或是清除定時(shí)器
數(shù)據(jù)更新過(guò)程
更新過(guò)程指父組件向下傳遞props或組件自身執(zhí)行setState方法時(shí)發(fā)生的一系列更新動(dòng)作带欢。
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更新了运授,那么會(huì)依次執(zhí)行 shouldComponentUpdate、componentWillUpdate乔煞、render和componentDidUpdate
外來(lái)組件prop傳遞進(jìn)來(lái)使組件更新
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è)當(dāng)前組件加載完時(shí)獲取當(dāng)前DOM,則可以使用findDOMNode
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
componentDidMount() {
// this 為當(dāng)前組件的實(shí)例
const dom = ReactDOM.findDOMNode(this);
}
render() {}
}
- render
ReactComponent render(
ReactElement element,
DOMElement container,
[function callback]
)
該方法把元素掛在到container中,并返回element的實(shí)例(即refs引用)纺讲。如果是無(wú)狀態(tài)組件擂仍,render會(huì)返回null。組件掛載完畢時(shí)熬甚,callback就會(huì)被調(diào)用逢渔。
與 render 相反,React 還提供了一個(gè)很少使用的 unmountComponentAtNode 方法來(lái)進(jìn)行
卸載操作
refs
ref -> reference
ref返回一個(gè)實(shí)例對(duì)象乡括,也可以是一個(gè)回調(diào)函數(shù)肃廓,focus的巧妙實(shí)現(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操作可以歸納為對(duì)DOM的增诲泌、刪盲赊、改、查敷扫“ⅲ“查”指的是對(duì)DOM屬性、樣式的查看葵第,比如DOM的位置绘迁、寬、高等信息羹幸。如果要調(diào)用HTML5 Audio/Video的play方法和input的focus方法脊髓,這時(shí)智能使用相應(yīng)的DOM方法來(lái)實(shí)現(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
})
}
計(jì)算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)存中是以對(duì)象的形式存在的将硝。React基于Virtual DOM實(shí)現(xiàn)了一個(gè)SyntheticEvent(合成事件)層,我們所定義的事件處理器會(huì)接收到一個(gè)SyntheticEvent對(duì)象的實(shí)例屏镊,它完全符合W3C標(biāo)準(zhǔn)依疼。
所有事件自動(dòng)綁定到最外層上,如需訪(fǎng)問(wèn)原生事件對(duì)象而芥,可以使員工nativeEvent屬性
合成事件實(shí)現(xiàn)機(jī)制
主要對(duì)合成事件做了兩件事:事件委派和自動(dòng)綁定律罢。
- 事件委派
事件代理機(jī)制。它并不會(huì)把事件處理函數(shù)直接綁定到真實(shí)的節(jié)點(diǎn)上棍丐,而是把所有事件綁定到結(jié)構(gòu)的最外層误辑,使用一個(gè)統(tǒng)一的事件監(jiān)聽(tīng)器,這個(gè)事件監(jiān)聽(tīng)器上維持了一個(gè)映射來(lái)保存所有組件內(nèi)部的事件監(jiān)聽(tīng)和處理函數(shù)歌逢。當(dāng)組件掛載或卸載時(shí)巾钉,只是在這個(gè)統(tǒng)一的事件監(jiān)聽(tīng)器上插入或刪除一些對(duì)象;秘案;當(dāng)事件發(fā)生時(shí)砰苍,首先被這個(gè)統(tǒng)一的事件監(jiān)聽(tīng)器處理潦匈,然后在映射里找到真正的事件處理函數(shù)并調(diào)用。 - 自動(dòng)綁定
每個(gè)方法的上下文都會(huì)指向該組件的實(shí)例赚导,即自動(dòng)綁定this為當(dāng)前組件
只綁定茬缩,不傳參,可以使用一種便捷的方案--雙冒號(hào)語(yǔ)法,babel已經(jīng)支持
<button onClick={this.handleClick.bind(this)}></button>
<button onClick={::this.handleClick}></button>
// 箭頭函數(shù)吼旧。自動(dòng)綁定定義此函數(shù)作用域的this凰锡,因此我們需要對(duì)它使用bind方法
const handleClick = (e) => {
console.log(e)
}
<button onClick={this.handleClick}></button>
// 或者
<button onClick={() => this.handleClick()}></button>
React中使用原生事件
componentDidMount會(huì)在組件已經(jīng)完成安裝并且在瀏覽器中存在真實(shí)的DOM后調(diào)用,因此我們就可以完成原生事件的綁定黍少。
值得注意的是寡夹,在 React 中使用 DOM 原生事件時(shí),一定要在組件卸載時(shí)手動(dòng)移除厂置,否則很可能出現(xiàn)內(nèi)存泄漏的問(wèn)題。在原生事件中阻止冒泡行為魂角,可以阻止React合成事件的傳播昵济,阻止React事件冒泡的行為,不能阻止原生事件的冒泡野揪。
對(duì)于無(wú)法使用React合成事件的場(chǎng)景访忿,我們還需要使用原生事件來(lái)完成。
對(duì)比React合成事件與JavaScript原生事件
- 事件傳播與阻止事件傳播
DOM事件傳播三個(gè)階段:事件捕獲階段斯稳、目標(biāo)對(duì)象本身的事件處理程序調(diào)用以及事件冒泡海铆。
React只有事件冒泡 - 事件類(lèi)型
- 事件綁定方式
原生事件綁定方式
1、直接在DOM元素綁定
<button onclick='alert(1)'></button>
2挣惰、JS中卧斟,通過(guò)為元素的事件屬性賦值的方式實(shí)現(xiàn)綁定
el.onClick = e => { }
3、通過(guò)事件監(jiān)聽(tīng)函數(shù)實(shí)現(xiàn)綁定(addEventListener)
React只有一種
<button onClick={this.handleClick.bind(this)}></button>
4憎茂、事件對(duì)象
React不存在兼容性問(wèn)題珍语,DOM事件對(duì)象在W3C和IE標(biāo)準(zhǔn)中存在差異。
表單
受控組件
每當(dāng)表單的狀態(tài)發(fā)生改變時(shí)竖幔,都會(huì)被寫(xiě)入到組件的state中板乙,這種組件在React中被稱(chēng)為受控組件(controlled Component),其上綁定了一個(gè)change事件拳氢,表單的數(shù)據(jù)元素組件的state募逞,并通過(guò)props傳入。
Reaact受控組件更新state的流程:
(1)可以通過(guò)在初始state中設(shè)置表單的默認(rèn)值
(2)每當(dāng)表單的值繁盛變化時(shí)馋评,調(diào)用onChange事件處理器
(3)事件處理器通過(guò)合成事件對(duì)象e拿到改變后的狀態(tài)放接,并更新應(yīng)用的state
(4)setState觸發(fā)視圖的重新渲染,完成表單組件值的更新
非受控組件
如果一個(gè)表單組件沒(méi)有value props(單選按鈕和復(fù)選框?qū)?yīng)的是checked prop)時(shí)栗恩,就可以稱(chēng)為非受控組件透乾。
受控組件和非受控組件的最大區(qū)別是:非受控組件的狀態(tài)并不會(huì)受應(yīng)用狀態(tài)的控制洪燥,應(yīng)用中也多了局部組件狀態(tài),而受控組件的值來(lái)自于組件的 state乳乌。
表單組件的幾個(gè)重要屬性
- 狀態(tài)屬性
- value:類(lèi)型為text的input捧韵,textarea組件以及select組件
- checked:類(lèi)型為radio,checkbox的組件借助boolean類(lèi)型的selected
- selected:該屬性可作用于select組件下面的option上
樣式處理
基本樣式處理
- 自定義組件建議支持className prop汉操,讓用戶(hù)使用時(shí)添加自定義樣式
- 設(shè)置行內(nèi)樣式時(shí)需要使用對(duì)象
使用 classnames庫(kù)來(lái)才做類(lèi)再来。
CSS Modules
Vjeux拋出了React開(kāi)發(fā)中遇到的一系列CSS相關(guān)問(wèn)題,有以下幾點(diǎn):
- 全局污染
- 命名混亂
- 依賴(lài)管理不徹底
- 無(wú)法共享變量
- 代碼壓縮不徹底
啟用CSS Modules
// webpack.config.js 的css-loader
css?modules&localIdentName=[name]__[local]-[hash:base64:5]
- 使用composes來(lái)組合樣式磷瘤,進(jìn)行樣式的復(fù)用
.base { /*所有通用樣式*/ }
.normal {
compose: base;
/* normal 其他樣式 */
}
- class命名技巧
BEM命名:
1芒篷、Block:對(duì)應(yīng)模塊名,如Dialog
2采缚、Element:對(duì)應(yīng)模塊中的節(jié)點(diǎn)名Confirm Button
3针炉、Modifier:對(duì)應(yīng)節(jié)點(diǎn)相關(guān)的狀態(tài),如disableed
組件中通信
父組件向子組件通信
通過(guò)props向子組件傳遞需要的信息子組件向父組件通信
1扳抽、利用回調(diào)函數(shù)
2篡帕、利用自定義事件機(jī)制
跨級(jí)組件通信
使用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對(duì)象里的方法都掛載到原對(duì)象上贸呢,來(lái)實(shí)現(xiàn)對(duì)對(duì)象的混入镰烧。
ES6 Classes與decorator:
ES并沒(méi)有改變JavaScript面向?qū)ο蠓椒ɑ谠偷谋举|(zhì)。decorator是運(yùn)用在運(yùn)行時(shí)的方法楞陷。
高階組件
higher-order function(高階函數(shù))在函數(shù)式 編程中是一個(gè)基本的概念怔鳖,它描述的是這樣一種函數(shù):這種函數(shù)接受函數(shù)作為輸入,或是輸出一 個(gè)函數(shù)固蛾。
高階組件(higher-order component)结执,類(lèi)似于高階函數(shù),它接受React 組件作為輸入魏铅,輸出一 個(gè)新的 React 組件昌犹。
實(shí)現(xiàn)高階組件的方法有如下兩種:
- 屬性代理(props proxy)。高階組件通過(guò)被包裹的React組件來(lái)操作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í)行聲明周期的過(guò)程類(lèi)似堆棧調(diào)用:
didmount -> HOC didmount -> (HOCs didmount) -> (HOCs will unmount) -> HOC will unmount -> unmount
高階組件的功能:
- 控制props
- 通過(guò)refs使用引用
import React, { Component } from 'react';
const MyContainer = (WrappedComponent) =>
class extends Component {
// 得到實(shí)例對(duì)象
proc(wrappedComponentInstance) {
// 執(zhí)行實(shí)例方法
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)用順序和隊(duì)列是一樣的:
didmount -> HOC didmount -> (HOCs didmount) -> will unmount -> HOC will didmount -> (HOCs will unmount)
它有兩個(gè)比較大的特點(diǎn):
- 渲染劫持
渲染劫持指的就是高階組件可以控制 WrappedComponent 的渲染過(guò)程,并渲染各種各樣的結(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庫(kù)中已經(jīng)實(shí)現(xiàn)了HOC.displayName =
HOC(${getDisplayName(WrappedComponent)})``
可以使用recompose庫(kù)
- 組件參數(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 { }
組件疊加使用
多個(gè)組件方法铸敏,疊加使用,可以利用 compose方法包裹
const FinalSelector = compose(asyncSelectDecorator,searchDecorator, selectedItemDecorator)(Selector);
性能優(yōu)化
1悟泵、純函數(shù)
2杈笔、pureRender
官方在早期就為開(kāi)發(fā)者提供了名為 react-addons-pure-render-mixin 的插件。其原理為重新實(shí)現(xiàn)了 shouldComponentUpdate生命周期方法糕非,讓當(dāng)前傳入的 props 和 state 與之前的作淺比較蒙具,如果返回 false球榆,那么組件就不會(huì)執(zhí)行 render 方法。
3禁筏、Immutable
4持钉、key
5、react-addons-perf
量化以上所做的性能優(yōu)化的效果篱昔。
動(dòng)畫(huà)
React Transition
生命周期
- componentWillAppear
- componentDidAppear
- componentWillEnter
- componentDidEnter
- componentWillLeave
- componentDidLeave
componentWillxxx 在什么時(shí)候觸發(fā)很容易判斷每强,只需在componentWillReceiveProps 中對(duì) this.props.children 和nextProps.children 做一個(gè)比較即可糯笙。而 componentDidxxx 要何時(shí)觸發(fā)呢?
可以給 componentWillxxx 提供一個(gè)回調(diào)函數(shù)蛮浑,用來(lái)執(zhí)行componentDidxxx。
緩動(dòng)函數(shù)
自動(dòng)化測(cè)試
Jest