轉(zhuǎn):http://www.reibang.com/p/9ab8a2e0403a
在多個(gè)不同的組件中需要用到相同的功能荧降,其解決辦法有兩種:mixin和高階組件慕购。
1牺丙、mixin
mixin一直被廣泛用于各種面向?qū)ο笳Z言中伪节,其作用是為單繼承語言創(chuàng)造一種類似多重繼承的效果。
廣義的mixin方法,就是用賦值的方式將mixin對(duì)象中的方法都掛載到原對(duì)象上,來實(shí)現(xiàn)對(duì)象的混入酒繁,類似ES6中的Object.assign()的作用。原理如下:
const mixin = function(obj, mixins){
const newObj = obj;
newObj.prototype = Object.create(obj.prototype);
for(let prop in mixins){ // 遍歷mixins的屬性
if(mixins.hasOwnPrototype(prop)){ // 判斷是否為mixin的自身屬性
newObj.prototype[prop] = mixins[prop]; // 賦值
}
}
return newObj;
}
實(shí)質(zhì)上就是把任意多個(gè)源對(duì)象擁有的自身可枚舉屬性復(fù)制給目標(biāo)對(duì)象控妻,然后返回目標(biāo)對(duì)象欲逃。那么React中的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>;
}
});
可以看出稳析,mixins是一個(gè)數(shù)組,封裝了我們需要的模塊弓叛。不同mixin的方法或許會(huì)有重合彰居,如何處理視重合部分是普通方法還是生命周期方法而定。
在不同mixin里實(shí)現(xiàn)兩個(gè)名字一樣的普通方法:并不會(huì)覆蓋撰筷,且控制臺(tái)會(huì)報(bào)錯(cuò)陈惰。
重合的是生命周期方法:將各個(gè)mixin的生命周期方法疊加在一起順序執(zhí)行。
可以看到毕籽,使用createClass實(shí)現(xiàn)的mixin為組件做了兩件事:
工具方法:mixin的基本功能抬闯。用來定義共享的工具類方法,以便在各個(gè)組件中使用关筒。
生命周期繼承溶握,props與state合并:mixin可以合并生命周期方法。如果有多個(gè)mixin定義了componentDidMount()蒸播,React會(huì)自動(dòng)將它們合并處理睡榆。同樣萍肆,mixin也可以作用在getInitialState的結(jié)果上,作state的合并胀屿,而props的合并也是這樣的塘揣。
然而,使用ES6 classes構(gòu)建組件時(shí)宿崭,并不支持mixin亲铡。這就不得不說到decorator語法糖。
ES6 classes 與 decorator
decorator是運(yùn)用在運(yùn)行時(shí)的方法葡兑,用以對(duì)組件進(jìn)行“修飾”〗甭現(xiàn)在,使用decorator來實(shí)現(xiàn)mixin:
function handleClass(target, mixins){
if(mixins.length){
for(let i=0, l=mixins.length; i<l; i++){
// 獲取mixins的attribute對(duì)象
const decs = getOwnPropertyDescriptors(mixins[i]);
}
// 定義mixins的attribute對(duì)象
for(const key in decs){
if(!(key in target.prototype)){
defineProperty(target.prototype, key, decs[key]);
}
}
}
}
function mixin(...mixins){
if(typeof mixins[0] === 'function'){
return handleClass(mixins[0], []);
}else{
return target=>{
return handleClass(target,mixins);
}
}
}
不難看出铁孵,這個(gè)mixin與本文開頭createClass的mixin的實(shí)現(xiàn)是不一樣的:createClass的mixin是直接給對(duì)象的prototype屬性賦值,而這里是使用getOwnPropertyDescriptors和defineProperty進(jìn)行定義房资。賦值與定義的區(qū)別在于賦值會(huì)覆蓋已有的定義蜕劝,而后者不會(huì)。兩者在本質(zhì)上都與官方的mixin方法存在區(qū)別轰异,除了定義方法級(jí)別不能覆蓋之外岖沛,還得加上對(duì)生命周期方法的繼承以及對(duì)state的合并。
當(dāng)然搭独,decorator除作用在類上婴削,還可以作用在方法上,但不在此處討論牙肝。
minxin的缺陷
破壞了原有組件的封裝:可能會(huì)帶來新的state和props,意味著會(huì)有些“不可見”的狀態(tài)需維護(hù)唉俗。
命名沖突:不同mixin中的命名不可知,故非常容易發(fā)生沖突配椭,需要花一定成本解決虫溜。
增加了復(fù)雜性,難以維護(hù)股缸。
2衡楞、高階組件
由于mixin存在上述缺陷,故React剝離了mixin敦姻。改用高階組件來取代它瘾境。
高階組件其實(shí)是一個(gè)函數(shù),接收一個(gè)組件作為參數(shù)镰惦,返回一個(gè)新的組件作為返回值迷守,類似于高階函數(shù)。高階組件和decorator是同一模式旺入,因此盒犹,因此高階組件可以作為decorator來使用。高階組件基本形式:
const EnhancedComponent = higherOrderComponent(WrappedComponent);
decorator形式:
@higherOrderComponent
WrappedComponent
高階組件有以下好處:
適用范圍廣,它不需要es6或者其它需要編譯的特性急膀,有函數(shù)的地方沮协,就有HOC。
Debug友好卓嫂,它能夠被React組件樹顯示慷暂,所以可以很清楚地知道有多少層,每層做了什么晨雳。
高階組件實(shí)現(xiàn)的方法有兩種:
屬性代理:通過被包裹組件的props來進(jìn)行相關(guān)操作行瑞。主要進(jìn)行組件的復(fù)用。
反向繼承:繼承被包裹的組件餐禁。主要進(jìn)行渲染的劫持血久。
1、屬性代理
屬性代理主要是四個(gè)作用:操作props帮非、通過refs訪問組件實(shí)例氧吐、抽象state、使用其他元素包裹WrappedComponent末盔。
(1)操作props
包括對(duì)props的讀取筑舅、增加、刪除陨舱、修改翠拣。刪除和修改要注意不能影響原組件。
示例:增加一個(gè)props
function compHOC(WrappedComponent) {
return class Comp extends React.Component {
render() {
const newProps = {
user: currentLoggedInUser
}
return <WrappedComponent {...this.props} {...newProps}/>
}
}
}
(2)通過refs訪問組件實(shí)例
可以通過ref回調(diào)函數(shù)的形式來訪問傳入組件的實(shí)例游盲,進(jìn)而調(diào)用組件相關(guān)方法或其他操作(如實(shí)例的props操作)误墓。
//WrappedComponent初始渲染時(shí)候會(huì)調(diào)用ref回調(diào),傳入組件實(shí)例益缎,在proc方法中优烧,就可以調(diào)用組件方法
function refsHOC(WrappedComponent) {
return class RefsHOC extends React.Component {
proc(wrappedComponentInstance) {
wrappedComponentInstance.method()
}
render() {
const props = Object.assign({}, this.props, {ref: this.proc.bind(this)})
return <WrappedComponent {...props}/>
}
}
}
(3)抽象state
通過傳入 props 和回調(diào)函數(shù)抽象state。高階組件可以通過原組件抽象為展示型組件链峭,分離內(nèi)部狀態(tài)畦娄。
示例:抽象 <Input />的 value 和 onChange 方法。
function compHOC(WrappedComponent) {
return class Comp extends React.Component {
constructor(props) {
super(props)
this.state = {
name: ''
}
this.onNameChange = this.onNameChange.bind(this)
}
// 將對(duì)name屬性的onChange方法提取到此處=>提取到高階組件弊仪,有效的抽象了同樣的state操作
onNameChange(event) {
this.setState({
name: event.target.value
})
}
render() {
const newProps = {
name: {
value: this.state.name,
onChange: this.onNameChange
}
}
return <WrappedComponent {...this.props} {...newProps}/>
}
}
}
//使用方式如下
@compHOC
class Example extends React.Component {
render() {
//使用ppHOC裝飾器之后熙卡,組件的props被添加了name屬性,可以通過下面的方法励饵,將 value 和 onChange方法 添加到input上面
return <input name="name" {...this.props.name}/>
// 變成<input name="name" value={this.state.name} onChange={this.onNameChange} />驳癌,這樣我們就得到了一個(gè)受控組件政基。
}
}
(4)使用其他元素包裹組件
用于加樣式惠猿、布局等。
function compHOC(WrappedComponent) {
return class Comp extends React.Component {
render() {
return (
<div style={{display: 'block'}}>
<WrappedComponent {...this.props}/>
</div>
)
}
}
}
2陕贮、反向繼承
高階組件繼承了WrappedComponent,意味著可以訪問并使用WrappedComponent的state甜滨,props乐严,生命周期和render方法,但它不能保證完整的子組件樹被解析衣摩。如果在高階組件中定義了與WrappedComponent中同名的方法昂验,將會(huì)發(fā)生覆蓋,就必須手動(dòng)通過super進(jìn)行調(diào)用艾扮。反向繼承有兩個(gè)比較大的特點(diǎn):渲染劫持和控制state既琴。
(1)渲染劫持
渲染劫持指的就是高階組件可以控制 WrappedComponent 的渲染過程,并渲染各種各樣的結(jié)果泡嘴。我們可以在這個(gè)過程中在任何React元素輸出的結(jié)果中讀取甫恩、增加、修改酌予、刪除props磺箕,或讀取或修改React元素樹,或條件顯示元素樹霎终,又或者是用元素包裹元素樹滞磺。
大致形式如下:
function compHOC(WrappedComponent) {
return class ExampleEnhance extends WrappedComponent {
...
componentDidMount() {
super.componentDidMount();
}
componentWillUnmount() {
super.componentWillUnmount();
}
render() {
...
return super.render();
}
}
}
例如升薯,實(shí)現(xiàn)一個(gè)顯示loading的請(qǐng)求莱褒。組件中存在網(wǎng)絡(luò)請(qǐng)求,完成請(qǐng)求前顯示loading涎劈,完成后再顯示具體內(nèi)容广凸。(條件渲染)
可以用高階組件實(shí)現(xiàn)如下:
function hoc(ComponentClass) {
return class HOC extends ComponentClass { // 繼承原組件
render() {
if (this.state.success) {
return super.render()
}
return <div>Loading...</div>
}
}
}
@hoc
export default class ComponentClass extends React.Component {
state = {
success: false,
data: null
};
async componentDidMount() {
const result = await fetch(...請(qǐng)求);
this.setState({
success: true,
data: result.data
});
}
render() {
return <div>主要內(nèi)容</div>
}
}
正如前面所說,反向繼承不能保證完整的子組件樹被解析蛛枚,這意味著會(huì)限制渲染劫持功能谅海。渲染劫持的經(jīng)驗(yàn)法則是:我們可以操控 WrappedComponent 的元素樹,并輸出正確的結(jié)果蹦浦。但如果元素樹中包括了函數(shù)類型的React組件扭吁,就不能操作組件的子組件。
(2)控制state
高階組件可以讀取盲镶,編輯和刪除WrappedComponent實(shí)例的state侥袜,可以添加state。不過這個(gè)可能會(huì)破壞WrappedComponent的state溉贿,所以枫吧,要限制高階組件讀取或添加state,添加的state應(yīng)該放在單獨(dú)的命名空間里宇色,而不是和WrappedComponent的state混在一起九杂。
例如:通過訪問WrappedComponent的props和state來做調(diào)試
export function IIHOCDEBUGGER(WrappedComponent) {
return class II 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>
)
}
}
}
3颁湖、組件命名
用HOC包裹的組件會(huì)丟失原先的名字,影響開發(fā)和調(diào)試例隆∩啵可以通過在WrappedComponent的名字上加一些前綴來作為HOC的名字,以方便調(diào)試裳擎。
參考react-redux實(shí)現(xiàn):
HOC.displayName = HOC(${getDisplayName(WrappedComponent)})
;
//或
class HOC extends ... {
static displayName = HOC(${getDisplayName(WrappedComponent)})
;
...
}
//getDisplayName
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName ||
WrappedComponent.name ||
‘Component’
}
4涎永、組件參數(shù)
有時(shí)候,在調(diào)用高階組件時(shí)鹿响,需要傳入一些參數(shù)羡微。可以這樣實(shí)現(xiàn):
function HocFactoryFactory(...params){
// 可以做一些改變params的事
return function HocFactory(WrappedCompinent){
return class Hoc extends Component {
render(){
return <WrappedComponent {...this.props} />
}
}
}
}
使用方式如下:
HocFactoryFactory(params)(WrappedComponent);
或者:
@HocFactoryFactory(params)
class WrappedComponent extends Component{
...
}
作者:南風(fēng)知我意ZD
鏈接:http://www.reibang.com/p/9ab8a2e0403a
來源:簡(jiǎn)書
著作權(quán)歸作者所有惶我。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)妈倔,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。