-
事件系統(tǒng)
- 合成事件的綁定方式
<button onClick={this.handleClick}>Test</button>
- 合成事件的實(shí)現(xiàn)機(jī)制:事件委派和自動綁定。
- React合成事件系統(tǒng)的委托機(jī)制,在合成事件內(nèi)部僅僅是對最外層的容器進(jìn)行了綁定,并且依賴事件的冒泡機(jī)制完成了委派。
-
表單
- React受控組件更新state的流程:
- 可以通過在初始state中設(shè)置表單的默認(rèn)值跑芳。
- 每當(dāng)表單的值發(fā)生變化時(shí),調(diào)用onChange事件處理器直颅。
- 事件處理器通過合成事件對象e拿到改變后的狀態(tài)博个,并更新應(yīng)用的state。
- setState觸發(fā)視圖的重新渲染际乘,完成表單組件值的更新坡倔。
- 受控組件和非受控組件的最大區(qū)別是:非受控組件的狀態(tài)并不會受應(yīng)用狀態(tài)的控制漂佩,應(yīng)用中也多了局部組件狀態(tài)脖含,而受控組件的值來自于組件的state。
-
樣式處理
- CSS模塊化遇到了哪些問題投蝉?全局污染养葵,命名混亂,依賴管理不徹底瘩缆,無法共享變量关拒,代碼壓縮不徹底。
- CSS Modules模塊化方案:啟用CSS Modules庸娱,樣式默認(rèn)局部着绊,使用composes來組合樣式。
-
組件間通信
- 子組件向父組件通信
- 利用回調(diào)函數(shù)
- 利用自定義事件機(jī)制
- 當(dāng)需要讓子組件跨級訪問信息時(shí)熟尉,我們還可以使用context來實(shí)現(xiàn)跨級父子組件間的通信归露。
- 沒有嵌套關(guān)系的組件通信:我們在處理事件的過程中需要注意,在componentDidMount事件中斤儿,如果組件掛載完成剧包,再訂閱事件;當(dāng)組件卸載的時(shí)候往果,在componentWillUnmount事件中取消事件的訂閱疆液。
-
組件間抽象
- mixin 的目的,就是為了創(chuàng)造一種類似多重繼承的效果陕贮,或者說堕油,組合。實(shí)際上,包括C++等一些年齡較大的OOP語言掉缺,都有一個(gè)強(qiáng)大但是危險(xiǎn)的多重繼承特性「R玻現(xiàn)代語言權(quán)衡利弊,大都舍棄了它攀圈,只采用單繼承暴凑。但是單繼承在實(shí)現(xiàn)抽象的時(shí)候有很多不便,為了彌補(bǔ)缺失赘来,Java引入接口(interface)现喳,其他一些語言則引入了mixin的技巧。
封裝mixin方法
方法:
const mixin = function(obj, mixins) {
const newObj = obj;
newObj.prototype = Object.create(obj.prototype);
for (let prop in mixins) {
if (mixins.hasOwnProperty(prop)) {
newObj.prototype[prop] = mixins[prop];
}
}
return newObj;
}
應(yīng)用:
const BigMixin = {
fly: () ={
console.log('I can fly');
}
};
const Big = function() {
console.log('new big');
};
const FlyBig = mixin(Big, BigMixin);
const flyBig = new FlyBig(); // ='new big'
flyBig.fly(); // ='I can fly'
上面這段代碼實(shí)現(xiàn)對象混入的方法是:用賦值的方式將mixin對象里的方法都掛載到原對象上犬辰。
- 在React中使用mixin
React在使用createClass構(gòu)建組件時(shí)提供了mixin屬性嗦篱,比如官方封裝的:PureRenderMixin。
import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';
React.createClass({
mixins: [PureRenderMixin],
render() {
return <div>foo</div>;
}
});
在createClass對象參數(shù)中傳入數(shù)組mixins幌缝,里面封裝了我們需要的模塊灸促。mixins數(shù)組也可以添加多個(gè)mixin。同時(shí)涵卵,在React中不允許出現(xiàn)重名普通方法的mixin浴栽。而如果是生命周期方法,則React將會將各個(gè)模塊的生命周期方法疊加在一起然后順序執(zhí)行轿偎。
使用createClass實(shí)現(xiàn)的mixin為組件做了兩件事:
- 工具方法:這是mixin的基本功能典鸡,如果希望共享一些工具類的方法,就可以直接定義它們?nèi)缓笤诮M件中使用坏晦。
- 生命周期繼承萝玷,props和state合并。mixin能夠合并生命周期方法昆婿。如果有很多mixin來定義componentDidMount這個(gè)周期生宛,那么React會很機(jī)智的將它們都合并起來執(zhí)行妄田。同樣,mixin也可以作state和props的合并。
- ES6 Classes和decorator
然而创淡,當(dāng)我們使用ES6 classes的形式構(gòu)建組件的時(shí)候粥谬,卻并不支持mixin宙地。為了使用這個(gè)強(qiáng)大的功能送漠,我們還需要采取其他方法,來達(dá)到模塊重用的目的狼荞×勺埃可以使用ES7的語法糖decorator來實(shí)現(xiàn)class上的mixin。core-decorators庫為開發(fā)者提供了一些實(shí)用的decorator, 其中也正好實(shí)現(xiàn)了我們想要的@mixin相味。
import React, { Component } from 'React';
import { mixin } from 'core-decorators';
const PureRender = {
shouldComponentUpdate() {}
};
const Theme = {
setTheme() {}
};
@mixin(PureRender, Theme)
class MyComponent extends Component {
render() {}
}
mixin的問題
- 破壞了原有組件的封裝:mixin會混入方法拾积,給原有的組件帶來新特性。但同時(shí)它也可能帶來新的state和props,這意味著組件有一些“不可見”的狀態(tài)需要我們?nèi)ゾS護(hù)拓巧。另外斯碌,mixin也有可能去依賴其他的mixin,這樣會建立一個(gè)mixin的依賴鏈肛度,當(dāng)我們改動一個(gè)mixin的狀態(tài)傻唾,很有可能也會影響其他的mixin。
- 命名沖突
- 增加復(fù)雜性
針對這些困擾承耿,React提出的新的方式來取代mixin冠骄,那就是高階組件。 - 高階組件
如果已經(jīng)理解高階函數(shù)加袋,那么理解高階組件也很容易的凛辣。高階函數(shù):就是一種這樣的函數(shù),它接受函數(shù)作為參數(shù)輸入职烧,或者將一個(gè)函數(shù)作為返回值扁誓。例如我們常見的方法map, reduce, sort等都是高階函數(shù)。高階組件和和高階函數(shù)很類似蚀之,高階組件就是接受一個(gè)React組件作為參數(shù)輸入蝗敢,輸出一個(gè)新的React組件。高階組件讓我們的代碼更具有復(fù)用性恬总、邏輯性與抽象性前普,它可以對render方法作劫持,也可以控制props和state壹堰。
實(shí)現(xiàn)高階組件的方法有如下兩種: - 屬性代理:高階組件通過被包裹的React組件來操作props。
- 反向繼承:高階組件繼承于被包裹的React組件骡湖。
屬性代理
示例代碼:
import React, { Component } from 'React';
const MyContainer = (WrappedComponent) =
class extends Component {
render() {
return <WrappedComponent {...this.props} />;
}
}
在代碼中我們可以看到贱纠,render方法返回了傳入的WrappedComponent組件。這樣响蕴,我們就可以通過高階組件來傳遞props谆焊。這種方式就是屬性代理。
如何使用上面這個(gè)高階組件:
import React, { Component } from 'React';
class MyComponent extends Component {
// ...
}
export default MyContainer(MyComponent);
這樣組件就可以一層層的作為參數(shù)被調(diào)用浦夷,原始組件久具備了高階組件對它的修飾辖试。這樣,保持單個(gè)組件封裝的同時(shí)也保留了易用行劈狐。
從功能上罐孝, 高階組件一樣可以做到像mixin對組件的控制:
- 控制props
我們可以讀取、增加肥缔、編輯或是移除從WrappedComponent傳進(jìn)來的props莲兢。
例如:新增props
import React, { Component } from 'React';
const MyContainer = (WrappedComponent) =
class extends Component {
render() {
const newProps = { text: newText, };
return <WrappedComponent {...this.props} {...newProps} />;
}
}
注意:
<WrappedComponent {...this.props}/>
// is equivalent to
React.createElement(WrappedComponent, this.props, null)
這樣,當(dāng)調(diào)用高階組件的時(shí)候,就可以使用text這個(gè)新的props了改艇。
- 通過refs使用引用
- 抽象state
高階組件可以講原組件抽象為展示型組件收班,分離內(nèi)部狀態(tài)。
const MyContainer = (WrappedComponent) =
class extends Component {
constructor(props) {
super(props);
this.state = { name: '', 4 };
this.onNameChange = this.onNameChange.bind(this);
}
onNameChange(event) {
this.setState({
name: event.target.value,
})
}
render() {
const newProps = {
name: {
value: this.state.name,
onChange: this.onNameChange,
},
}
return <WrappedComponent {...this.props} {...newProps} />;
}
}
在這個(gè)例子中谒兄,我們把組件中對name prop 的onChange方法提取到高階組件中摔桦,這樣就有效的抽象了同樣的state操作。
使用方式
@MyContainer
class MyComponent extends Component {
render() {
return <input name="name" {...this.props.name} />;
}
}
反向繼承
const MyContainer = (WrappedComponent) =
class extends WrappedComponent {
render() {
return super.render();
}
}
-
組件性能優(yōu)化
性能優(yōu)化的思路
影響網(wǎng)頁性能最大的因素是瀏覽器的重排(repaint)和重繪(reflow)承疲。React的Virtual DOM就是盡可能地減少瀏覽器的重排和重繪酣溃。從React渲染過程來看,如何防止不必要的渲染是解決問題的關(guān)鍵纪隙。
性能優(yōu)化的具體辦法
- 盡量多使用無狀態(tài)函數(shù)構(gòu)建組件
無狀態(tài)組件只有props和context兩個(gè)參數(shù)赊豌。它不存在state,沒有生命周期方法绵咱,組件本身即有狀態(tài)組件構(gòu)建方法中的render方法碘饼。在合適的情況下,都應(yīng)該必須使用無狀態(tài)組件悲伶。無狀態(tài)組件不會像React.createClass和ES6 class會在調(diào)用時(shí)創(chuàng)建新實(shí)例艾恼,它創(chuàng)建時(shí)始終保持了一個(gè)實(shí)例,避免了不必要的檢查和內(nèi)存分配麸锉,做到了內(nèi)部優(yōu)化钠绍。 - 拆分組件為子組件,對組件做更細(xì)粒度的控制
相關(guān)重要概念:純函數(shù)
純函數(shù)的三大構(gòu)成原則:
- 給定相同的輸入花沉,它總是返回相同的輸出: 比如反例有 Math.random(), New Date();
- 過程沒有副作用:即不能改變外部狀態(tài);
- 沒有額外的狀態(tài)依賴:即方法內(nèi)部的狀態(tài)都只能在方法的生命周期內(nèi)存活柳爽,這意味著不能在方法內(nèi)使用共享的變量。
純函數(shù)非常方便進(jìn)行方法級別的測試及重構(gòu)碱屁,它可以讓程序具有良好的擴(kuò)展性及適應(yīng)性磷脯。純函數(shù)是函數(shù)式變成的基礎(chǔ)。React組件本身就是純函數(shù)娩脾,即傳入指定props得到一定的Virtual DOM赵誓,整個(gè)過程都是可預(yù)測的。
具體辦法
拆分組件為子組件柿赊,對組件做更細(xì)粒度的控制俩功。保持純凈狀態(tài),可以讓方法或組件更加專注(focus)碰声,體積更小(small)诡蜓,更獨(dú)立(independent),更具有復(fù)用性(reusability)和可測試性(testability)奥邮。
- 運(yùn)用PureRender万牺,對變更做出最少的渲染
相關(guān)重要概念: PureRender
PureRender的Pure即是指滿足純函數(shù)的條件罗珍,即組件被相同的props和state渲染會得到相同的結(jié)果。在React中實(shí)現(xiàn)PureRender需要重新實(shí)現(xiàn)shouldComponentUpdate生命周期方法脚粟。shouldComponentUpdate是一個(gè)特別的方法覆旱,它接收需要更新的props和state,其本質(zhì)是用來進(jìn)行正確的組件渲染核无。當(dāng)其返回false的時(shí)候扣唱,不再向下執(zhí)行生命周期方法;當(dāng)其返回true時(shí)团南,繼續(xù)向下執(zhí)行噪沙。組件在初始化過程中會渲染一個(gè)樹狀結(jié)構(gòu),當(dāng)父節(jié)點(diǎn)props改變的時(shí)候吐根,在理想情況下只需渲染一條鏈路上有關(guān)props改變的節(jié)點(diǎn)即可正歼;但是,在默認(rèn)情況下shouldComponentUpdate方法返回true,React會重新渲染所有的節(jié)點(diǎn)拷橘。
有一些官方插件實(shí)現(xiàn)了對shouldComponentUpdate的重寫局义,然后自己也可以做一些代碼的優(yōu)化來運(yùn)用PureRender。
具體辦法
- 運(yùn)用PureRender
使用官方插件react-addons-pure-render-mixin實(shí)現(xiàn)對shouldComponentUpdate的重寫
import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';
class App extends React.Component {
constructor(props) {
super(props);
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
}
render() {
return <div className={this.props.className}>foo</div>
}
}
它的原理是對object(包括props和state)做淺比較冗疮,即引用比較萄唇,非值比較。比如只用關(guān)注props中每一個(gè)是否全等(如果是prop是一個(gè)對象那就是只比較了地址术幔,地址一樣就算是一樣了)另萤,而不用深入比較。
- 優(yōu)化PureRender
避免無論如何都會觸發(fā)shouldComponentUpdate返回true的代碼寫法诅挑。避免直接為prop設(shè)置字面量的數(shù)組和對象,就算每次傳入的數(shù)組或?qū)ο蟮闹禌]有變四敞,但它們的地址也發(fā)生了變化。
如以下寫法每次渲染時(shí)style都是新對象都會觸發(fā)shouldComponentUpdate為true:
<Account style={color: 'black'} />
改進(jìn)辦法:將字面量設(shè)置為一個(gè)引用:
const defaultStyle = {};
<Account style={this.props.style || defaultStyle} />
避免每次都綁定事件,如果這樣綁定事件的話每次都要生成一個(gè)新的onChange屬性的值:
render() {
return <input onChange={this.handleChange.bind(this)} />
}
該盡量在構(gòu)造函數(shù)內(nèi)進(jìn)行綁定揍障,如果綁定需要傳參那么應(yīng)該考慮抽象子組件或改變現(xiàn)有數(shù)據(jù)結(jié)構(gòu):
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange() {
...
}
render() {
return <input onChange={this.handleChange} />
}
在設(shè)置子組件的時(shí)候要在父組件級別重寫shouldComponentUpdate目养。
- 運(yùn)用immutable
JavaScript中對象一般是可變的,因?yàn)槭褂靡觅x值毒嫡,新的對象的改變將影響原始對象。為了解決這個(gè)問題是使用深拷貝或者淺拷貝幻梯,但這樣做又造成了CPU和內(nèi)存的浪費(fèi)兜畸。Immutable data很好地解決了這個(gè)問題。Immutable data就是一旦創(chuàng)建碘梢,就不能再更改的數(shù)據(jù)咬摇。對Immutable對象進(jìn)行修改、添加或刪除操作煞躬,都會返回一個(gè)新的Immutable對象肛鹏。Immutable實(shí)現(xiàn)的原理是持久化的數(shù)據(jù)結(jié)構(gòu)逸邦。即使用舊數(shù)據(jù)創(chuàng)建新數(shù)據(jù)時(shí),保證新舊數(shù)據(jù)同時(shí)可用且不變在扰。同時(shí)為了避免深拷貝帶來的性能損耗缕减,Immutable使用了結(jié)構(gòu)共享(structural sharing),即如果對象樹中一個(gè)節(jié)點(diǎn)發(fā)生變化,只修改這個(gè)節(jié)點(diǎn)和受它影響的父節(jié)點(diǎn)芒珠,其他節(jié)點(diǎn)則進(jìn)行共享桥狡。
-
自動化測試
jest 是 facebook 開源的,用來進(jìn)行單元測試的框架皱卓,功能比較全面裹芝,測試、斷言娜汁、覆蓋率它都可以嫂易,另外還提供了快照功能。
對測試群眾來說掐禁,從質(zhì)量保證的角度出發(fā)怜械,單元測試覆蓋率100%是否就足夠了呢?肯定不夠澳鹿稹宫盔!
結(jié)合實(shí)際的項(xiàng)目經(jīng)驗(yàn)來看,jest的測試還可以根據(jù)產(chǎn)品的實(shí)際需求享完,做一些諸如:
點(diǎn)擊某個(gè)頁面元素后灼芭,需要在頁面上顯示新的區(qū)塊,并且要加載指定的的css的測試;
點(diǎn)擊某個(gè)link般又,需要跳轉(zhuǎn)到指定的網(wǎng)站的測試;
等等
這些測試原本在UI自動化功能測試中也比較常見彼绷,這里我們都可以把它們挪到低層中去。所以具體的測試用例茴迁,在單元測試覆蓋率超級高的前提下寄悯,我們測試的群眾還可以跟研發(fā)結(jié)對完成《橐澹或者指導(dǎo)研發(fā)完成猜旬,要不干脆自己加上去算了。另外倦卖,產(chǎn)品的功能性測試完成的情況下洒擦,我們還需要考慮下非功能性的問題,例如兼容性怕膛、性能熟嫩、安全性等。再加上測試金字塔的頂端之上褐捻,其實(shí)還有探索性測試的位置掸茅。產(chǎn)品的基本功能由單元測試保障了椅邓,剩下的時(shí)間,我們可以做更多的探索性測試了不是嗎?總之昧狮,干掉UI自動化功能測試只是一個(gè)加速測試反饋周期景馁、減少投入成本的嘗試。軟件的質(zhì)量不僅僅是測試攻城獅的事情陵且,而是整個(gè)團(tuán)隊(duì)的責(zé)任裁僧。堅(jiān)持一些重要的編碼實(shí)踐,比如state less的組件慕购、build security in等聊疲,也是提高質(zhì)量的重要手段。