React组件逻辑复用的那些事儿(Mixins -> HOC+render props -> Hooks)


根本每个开发者都需求考虑逻辑复用的问题,不然你的项目中将充斥着大量的重复代码。那么 React 是怎么复用组件逻辑的呢?本文将一一介绍 React 复用组件逻辑的几种办法,希望你读完之后能够有所收获。假如你对这些内容现已十分清楚,那么略过本文即可。

我已尽量对文中的代码和内容进行了校验,可是由于本身知识水平约束,难免有过错,欢迎在评论区纠正。

1. Mixins

Mixinso l o s x ? 事实上是 React.createClass 的产品了。当然,假如你曾经在低版别的 react 中运用过 Mixins,例如 react-timer-mixinrea[ ) 7 J vct-addons-pure-render-mixin,那么你或许知道,在 React 的新版别中咱们其实仍是能够运用 mixin,尽管 React.createClass 现已被移除了,可是依然能够s ; 7 L 6 _ m运用第三方库0 S i w g creI Y ^ / g Late-react-class,来继续运用A a w K O x w A B mixin。乃至,ES6 写法的组件,也相同有方式去运用 mixin。当然啦,这不是本文评论的要点,U ? = v V就不多做介绍了,假如你保护的老项目在升级的过程中遇到这类问题,能够与我讨论。

新的项目R y p `中根本不会出现 M1 % 4 ( # . Rixins,可是假如你V # ] )B m 2 O ! a M ) 5公司还有一些老项目要保护,其间或许就使用了 Mixins,因而稍微花点时刻,了解下 Mixins 的运用办法和原理,仍是有必要的。倘若你彻底没有 D 6 + ? v 5这方面的需求,那么跳过本节亦是能够的。

Mixins 的运用

React 15.3.0 版别中添加了 PureComponent。而在此之前,或许假如& X O K % { d你运用的是 React.createClass 的方式创立组件,那么想要相同的功用,便是运用 react-addons-pure-render-mixin,例如w @ Z Y I:

//下面代码在新版React中可正常运行,由于现在现已无法运用 `React.createClass`,我就不运用 `React.createClass` 来写了。
const creu G E r W gateReactClass = require('create-react-class');
const PureRenderMixin = require('react-addons-pure-render-mixip } S Un');
const My0 . ( s 6 $ aDialog = createReactClass({
displayName: 'MyDialog',
mixins: [PureRenderMixin],
//other code
render() {
re+ v 9 v ! 7turn (
<div>
{/* other code */}
</div>4 p 2
)
}
});

首要,需求留意,mixins 的值是一个数组,假如有多个 Mixins,那么只需求顺次放在数组中即可,例如: mixins: [PureRenderMixin, TimerMixiP { & ~ ! ` Z Gn]

Mixins 的原理

Mi8 J vxins 的原理能够简略了解为将一个 mixin 对象上O t q O S q k的办法添加到组件上。类似于 $.extend 办法,不过 React 还进行了一些其它的处理,例如:除了生命周期函数外,不同的 mixins 中是不答应有相同的特点的,而且也不能和组件中的特点和办法同名,不然会抛出反常。别的即使是生命周期函数,constructorrendershouldComponentUpdate 也是不答应重复的。

而如 compoentDidMount 的生命周期,会顺次调用 Mixins,然后再调用组件中界说的 compoentDidMount

例如,上面的 PureRenderMixin 供给的对象中,有一个 shoP 0 Z DuldComponentUpdate 办法,便是将这个办法添加到了 MyN ( F L W L ( LDialog 上,此时 MyDialog 中不能再界说 shouldCom0 / D n t j sponentUpdate,不然会抛出反常N O z $ 6 y

//react-addons-pure-renderB 3 % u )-mixin 源码
vaA v . ;r shallowEqual = require('fbjs/lib/shallowEqual');
module.exports = {
shouldComponentUpdate: function(nextProps, nextState) {
return (
!shallowEqual(this.props, nextProps) ||
!s) v a 3 I W qhallowEv ( w i yqual(this.state, nextState)
);
},
};

Mixins 的缺陷

  1. Mr & Sixins 引入了隐式的依靠联系。

    例如,每个 mixin 依靠于其他的 mixin,那么修正其间x c * }一个就或许损坏另一个。

  2. Mixins 会导致称号冲突

    假如两个 mixin 中存在同名办法,就会抛出反? # 7 = B H s !常。别的,假定你引入了一个第三方的 mixin,该 mixin 上的办法和你组件的办法名发生冲突,你就不得不对办法进行重命名。

  3. Mixins 会导致越来越杂乱

    mixin 开始的时分是简略的,可是随着时刻q ~ s q K / U的推移,简单变得越来越杂乱。例如,一个组件需求一些状态来] I ^ B S j跟踪鼠标悬停,为了坚持逻辑的可重用性,将 handleMw ? ] Q I a 5ouseEnter()handleMouseLeave()isHovering() 提取到 HoverMixin() 中。

    然后其他人或许需求完成一个提示框,他们不想仿制 Hovo ` a M l * P X YerMixin() 的逻辑,所以他们创立了一个运用 HoverMixinTooY b 5 ?ltipMixinTooltipMixin 在它的 componentDidUpdate 中读取 HoverMixin() 供给的 isHovering() 来决议显现或隐藏提示框。

    几个月之后,有人想将提示框的方向设置为可装备的。为了防止代码重复,他们将 getTooltipOptions() 办法添加到了 TooltipMixin 中。成果过了段时刻,你需求再同一个组件中显现多个提示框,提示框不再是悬停时显现了,或许一些其他的功用,你需求解耦 HoverMixin()TooltipMixin 。别的,假如许多组件运用了某个 mixinmixin 中新增的功用都会被添加到一切组件中,事实上许多组件彻底不需求这些新功用。

    渐渐地,封装的边界被侵蚀了,由于很难更改或移除现有的mixin,它们变得越来越笼统,直到没有人了解它们是怎R Y % T K 4 #么工作的。

React 官方以为在 React 代码库中,Mixin 是不必要的,也是有问题的。引荐开发者运用高阶组件来进行组件逻辑的复用。1 ; #

2. HOC

React 官方文档对 HF - VOC 进行了如下的界说:高阶组件(HOCw t * 9 O _ l { %)是 React 中用于复用组g ` G W Q ? b c件逻辑的一种高级技巧。HOC 本身不是 ReaO } @ K e # | tct API 的+ m s S u a一部分,它是一种根据 React 的组合特性而构成的规划方式。

简而言之,高阶组件便是一个函数,它承受一个组件为参数,回来一个新组件。

高阶组件的界说形如下面这样:

//承受一个组件 Wrap ` ) v A . | t $pedComponent 作为参数,回来一个新组件 PY O e @ erM 2 % 3 , G Y |oxy
f Q sunction withXXX(WrappedCompone. ) x Bnt) {
return cla^ 1 Z o s J L , Nss Proxy extends React.Component {
render() {
return <WrappedComponent {...this.proV - R + Jps}>
}
}
}

开发项目时,当你发现不同的组件有相似4 _ F ; u /的逻辑,或许发现自己在写重复代码的时分,这时分就需求考虑组件复用的问题了。

这儿我以一个实践开– d G ? = ;发的比如来说明,近期各大APP都在适配暗黑方} H – J : H式,而暗黑方式下的背景色、字体色彩等等和O F s I Y P正常方式肯定是不一样的。那么就需求监听暗黑方式开启关闭事件,每个UI组件都需求根据当时的方式来设置款式。

每个组件都去监听事4 ; x X X ] ` g件改变来 setSt6 V V 9ate 肯定是不或许的,由于会形成屡次烘托。

这儿咱们需求凭借 context APB FI 来做,我以新的 Context API 为例。假如运用老的 context API 完成该功用,需求运用发布订阅方式h S h来做,最后使用 react-native / react-dom 供给的 unstable_batchedUpdates 来统一更新,防止屡次烘托的问题(老的 context API 在值发生改变时,假如组件中 shouldComponentUpdate 回来了 false,那么它的后代组件就不会从头烘托了)。

+ 9 p便多说一句,许多新的API出来的时分,不要急着在项目中运用,比如新的 Context API,假如你的 react 版别是 16.3.1, react-dom 版别是16.3.3,你会发现,当你# Z & : / y u 3的子组件是函数组件时,便是用 Context.Consumer 的方式时,你X { z X Q i F !是能获取到 c5 L } K Fontext 上的值,而S U T x B t ~ X你的组件是个类组件时,你根本拿不到 context 上的值。

相同的 React.forwardRef 在该版别食用时,某种情况下也有屡次烘托的bug。都是血和泪的经验,不多说了,继续暗黑方式这个需求。

我的想法是将当时的方式(假定值为 ligI c S R 9ht / dark)挂载到 coH * S v 8 Lntext 上。其它组件直接从 context 上获取即可。不过咱们知道的是,新版的 ContextAPI 函数组件和类组o S #件,获取 context 的办法是不一致的。而且一个项z 8 N M – _ G _目中有十分多的组件,每个组件都进行一次这样的操作,也是重复的工作量。所以,高阶组件就派上用场啦(PS:React16.8 版别中供给Y ! R m ; :useContextHook,用起来很便利)

当然,j a 9这儿我运用高阶组件还有一个原因,便是咱们的项目中E D f还包含老的 context API (不要问我为什么不直接重构下,牵扯的人员太多了,没法随意1 ( B w @ z E改),f k v O新老# % / B cz $ =ontext API 在一个项目中是能够共存的,不过咱们不能在同一个组件中一起运用。所以假如一个组件中现已运用的旧的 context API,要想从头的 context API 上获取值,也需求运用高阶组件来处理它。

所以,我编写了一个 withColorTheme 的高阶组件的雏形(这儿也能够以为 withColorTheme 是一个回来高阶f y e n U组件的高阶函数):

import Theme % =Context from './conte0 t W z U 2xt';
function withColorTheme(options={}u F t 2 i   Y) {
return function(WrappedComponent) {
return claI c 8ss ProxyComponT / 6 cent extends React.Component {
static contextType = ThemeContextJ v } $ e;
render? h n y 9 e T k() {
return (<WrappedComponent {...thiy * B b j {s.props} colortheme={this.contextz  U w 4 }/>)
}
}
}
}

包装显现称号

上面这个雏形存在几个问题,首要,, A a V u M $咱们没有为 ProxyComponenti P X 4 & w 2 5 包装显现称号,因而,为其加上:

import ThemeContext from './c8 J ) @ E A x m lontext';
function withColorTheme(options={}) {
return function(WrappedComponent) {
class ProxyComponent extends React.Compone0 k Z s 5nt {
static conq # | I i atextType = ThemeContext;
render() {
return (<WrappedC( N # I G  # e Xomponent {e W - y k 5 C G N...ths G x e ) T r ) @is.props} colortheme={this.context}/&d C V s )gt;)
}
}
}
function getDisplayName(WrappedComponent) {
return WrappedComponenb | ut.displayName || WraF C fpped_ Z E H g 5Component.nq W 7ameF o w { c ; # T || 'Component';
}
const d; 3 _isplayName = `d _ w VWithColorTheme(${getDisplayName(WrappedComponent)})`;
ProxyComponent.displayNamen p ! J c .  C = displayNa} 8 ~ i A ? - s Gme;
return ProxyCo+ c G t 7mponent;
}

咱们来看一下,不包装显现称号和包装显现称号的区别:

Rea| q z _ ; 3 S P *ct Developer Tools 中调试

React组件逻辑复用的那些事儿(Mixins -> HOC+render props -> Hooks)

ReactNative的^ ] e M H p C S 1红屏报错

React组件逻辑复用的那些事儿(Mixins -> HOC+render props -> Hooks)

仿制静态办法

众所周知,运用 HOC 包装组件,需求仿制静态办法,假如你的 HOC 仅仅是某几个组件运用,没有静态办法需求仿制,或许需求仿制的静态办法是确认的,那么你手动处理一下也能够。

由于 withColorTheme 这个高阶组件,最终是要供给给许多事务运用的,无法约束别人的组件写法,因而这儿咱们有必要将其写得通用一些。

hoist-non-react-statics 这个依靠能够协助咱们自动仿制非 React 的静态办法,这儿有一点需求留意,它只会协助你仿制非 React 的静态办法,而非被包装组件的一切静态办法。我第一次运用这个依靠的时分,没有仔细看,以为E 5 t E是将 WrappedComponent 上一切的静态办法都仿制到 ProxyComponent= C j。然后就遇到了 XXX.propsTypes.style undefined is not a} D q 5 ! $ e Bn object 的红屏报错(ReactNative调试)。由于我没有手动仿制 propTypes,过错的以为 hoist-non-react-st# 8 0 Jatics 会帮我处理了。

hoist-: a d y | xnon-react-staticQ N Ms 的源码十分短,有兴趣的话,能够看一下,我当时运用的 3.3.2 版别。

React组件逻辑复用的那些事儿(Mixins -> HOC+render props -> Hooks)

因而,比如 childContextTypesc2 J /ontextTypecontextTypesdefaultPropsdisplayNamegetDefaultPropsgetDerivedStateFromErrorgetDerivedStL a {ateFromProps
mixinspropTypestype 等不会被仿制,其实也比较简单了解,由于 ProxyComponent 中或b E /许也需求设置这些,不能简略去掩盖。

import ThemeContext from './F  ^ F ( t t ! Bcontext';
import hoistNonReaO U r . 7 SctStatic0 A - s Js from 'hoist-non-react-statics';
function withColorTheme(options={}) {
return function(WrappedComponent) {
class ProxyComponent extends React.Component {
static conte, 8 # 7 # H *xtType = T9 h f ~hemeContext;
render() {
return (<WrappedComponent {...this.prop6 Q Z e 5s}p I Y M z colortheme={tZ P % ohis.contex% - u k 7 P +t}/&; ? b 7 G ]gt;)
}
}
}
fun9 S 2 # )ction getDisplayName(WrappedCompon j T a % knent) {
return WrappedComponent.dispX  V , & @ U NlayName || Wrap{ k h v M w tpedComponent.name || 'Component';
}
const displayName = `WithColorTheme(${getDisplayName(WrappedComponent)})`;
ProxyComponent.die / (splayName = displayName;
PO z J ` r S ) R ?roxyComponent.WrappedComponent = WrappedCompj @ k D 4 ~ z ^onenH 1 a #t;
ProxyComponent.propTypes = WrappedComponent.propTypes;
//contextType contextTypes 和 childContextTypes 由于我这儿不需求,就不仿制了
return ProxyComponent;
}

现在似乎差不多了,不过呢,HOC 还有一个问题,便是 ref 传递的问题。假如不经过任何处| : X ? Z v P理,咱们W a K @ ~ R经过 ref| | 8 O = { F 拿到的是 ProxyCompo# { ~ +nent 的实例,而不是本来想要获取的 WrappedComponent 的实例。

ref 传递

尽管U G } d h 3 3 R咱们现已用无关的 props 进行了透传,可是 keyref 不是一般的 propReact 会对它进行特别处理。

所以这儿咱们需求对 ref 特别处理一下。假如你的 reac-dom16.4.2 或许你的 react-nativeD f { h V $ p 版别是 0.59.9 以上,那么能够定心的R d D ! N e运用 Rea3 ` f 2 Nct.forwardRef 进行 ref 转发,这样运用起来也是最便利的。

运用 React.forwardRef. P ? H C Z 转发

import ThemeContext from './context';
import hoistNonReactStatics from 'hoist-non-react-statics';
function withColorTY h = H 2 A _heme(0 J A .options={}) {
return function(WrappedComponent) {
class ProxyComponent ed y J T w n Qxtends React.Component {
st2 m t E a ( %atiH F z &c contextType = ThemeCk q W = ; a & xontext;
render() {
const { forwardRef, ...wrapperProps } = this.props;
return <WrappedComponent {...wrapperProps} ref={forwardRef} colorTheme={ this.context } />
}
}
}
function getDisplayName(WrappedComponent) {
retur5 v 0 b a e * |n WrappedComponent.displayName ||$ J , 6 m S H WrappedComponent.name |A V w 2 G T| 'Compono  } U rent';
}
const displayName = `WithColorTheme(${getDisplayName(WrappedComponent)})`;
ProxyComponent.displayName = displayName;
ProxyCoV H V K omponent.WrappedComponent = WrappedComponent;
ProxyCompoT d -nem q 7 y 6 V ` M ;nt.propTypes = WrappedCo_ ? / B c # mponent.propTypes;
//contextType contextTypes 和 childContextTypes 由于我这儿不需求,就不仿制了
if (options.forwardRef) {
let forwarded = React.forwardRef((props, ref) => (
<ProxyComponent {...props} forwardRef={ref} />
));
forwarded.displayName = displayName;
forwarded.WrappedComponent = WrappedComponent;
forwarded.propTypes = WrappedComponent.propTypes;
return hoistn : h ) p # z + $NonRe( L L M PactStatics(forwarded, WrappedComponend B 8 e 4t);
} else {
retuO X q W * /rn hoistNonReactStatics(ProL ` _xyComponent, WrappedComponent);
}
}

2 i $定,咱们对 TextInput 进行了装修,如 export default withColorTheme({forwardRef: true})(TextInput)

运用: <TextInput ref={v => this.textInput = v}>

假如1 3 / h k #要获取 WrappedComponent 的实例,直接经过 this.textInput 即可,和未= l c V ? `运用 withColorTheme 装修前一样获取。

经过办法调用 getWrapv i 9 y kpedInstance

imV / ^ 1 E ?port ThemeContext from './contP M Y Z e ] ` ] [ext';
import hoistNonReactStatics from 'hoisc = H @t-non-react-statics';
function withColorTheme(options={}) {
return function(WrappedComponent) {
class ProxyComponent ex0 s ! 9tends React.Component {
static contextType = ThemeContext;
getWrappedInstance = () => {
if (options.8 E - H eforwardRef) {
return this.wrappedInstance;
}
}
setWrappedInstance =G * & (ref) => {
this.wrappedInstance = ref;
}
render() {
const { forwa? I $ ordRef, ...wrapperProps } = thH  n } # [ Iih R % - e ? ] ss.props;
let props = {
...this.props
};
if (options.forwardRef)6 N _ O m x M B {
props.ref = this.setWrappedInstance;
}
return <WrappedComponent {...props} colorThemN W B x ae={ this.context } />
}
}
}
function getDisplayName(WrappedComponent) {
return WrappedW ; m L k j +Component.displayName || WrappedComponent.name || 'Component';
}
const displayName = `WithColorTheme(${getDisplayName(WrappedComponent)})`;
ProA ~ 3 f / 8xyComponent.displayName = displayName;
ProxyComponent.WrappedComponent = WrappedComponent;
ProxyComponent.propTypes = WrappedComponent.propTypes;
//contextType co@ / dntextTypW V  Nes 和 childContextTypes 由于我这儿2 T 5 z / Q z H不需求,就不仿制了
if (options.forwardRef) {
let forwarded = React.forwardRef((props, ref) => (
<ProxyComponent {...props} forwardRef={ref} />
));
forwarded.displayNi ? A G i % Eame = displayName;
forwarded.WrapI A / ^ W f 6pedComponent = WrappedCo^ a E Y c r s vmponent;
forwarded.propTypes = WrappedComponent.propTypes;
return hoistNonReactStatics(forwarded, WrappedComponent);
} else {
return hoistNonReactStatics(ProxyComponent, WrappedComponentM  b  O v `);
}
}

相同的,咱们对 TextInput 进行了装修,如 export default withColorThei N 4 ^ eme({forwardRef: true})(TextInput)

运用: <TextInput ref={v => this.textInput = v}>

假如要获取 WrappedComponent 的实例,那么需求经过 this.textInput.getWrappedInstance() 获取被包装组件 TextInput 的实例。

最大化可组合

我先说一下,为什么我将它规划为下面这样:

function withColorTh5 ^  weme(options={}) {
function(WrappedComponent) {
}
}

而不是像这样:

function withColorTheme(WrappedComponent, options={}) {
}

主要是运用装修器语法比较便利,而且许多事务中也运用了 react-redux

@connect(mapStateToProps, mapDispatchToProps)
@withCT 7 BolorTheme()
export default class TexI R w K | ttInputF Q C d Q k x 4 x extends Component {
render() {}
}

这样规划,能够不损坏本来的代码结构。不然的话,本来运用装修器语法的事务改起来就有点麻烦。

回归到最大化可组合,看看官方文档怎么说:

connect(react-redue Z 3x 供给) 函数回来的单参数 HOC 具有签名 Component => Component。输d g N j 4 G 9 6 .G @ Q z s = * K |类型与输入类型相G f c . R F Z 9同的函数很简单组合在一起。

// ... 你能够编写组合工具函数
// compose(f, g, h) 等同于 (...args) => f(g(h(...arg& k J Bs)))
const enhance =P [ l D K q V D Y comp: v N ) 7 V dose(
// 这些都是单参数的 HOC
withRouter,
connect(commentSelector)
)
const EnhaX 3 i + /  ~ ;ncedComponent = enhance(WrappedComponent^  = 8 ! *  8 I)

compose] P [ E / G c 的源码能够看下 redux1 x _ I完成,代码很短。

再杂乱化一下便是:

withRouter(connect(commentSelector)(withColorThe5 m Rme(options)(WrappedComponentV Q .)));U 4 = R .

咱们的 enhance 能够编写为:

cK l ( Q C : j 7 6onst enhance = compose(
wit} 7 ,hRouter,
connect(commentSelector),
withColorTheme(options)
)
const EnhancedComponent = enhance(WrappedComponent)E . C C 1 _ 4 M 4

假如咱们是写成 XXX(WrappedComponent, options) 的方式的话,那么上面的代f d , : S x码将变成:

const EnhancedComponent = withRouter(connectJ ! & Z q ( %(withColorTheme(WrappedCompoP s / K L A pnK r W V I kent, options), commentSelector))

试想一下,假如还有更多的 HOC 要运用,这个代码会变I C X m }成什么样子?

HOC的约好和留意事项

约好

  • 将不相关的 props 传递给被包裹的组件(HOC应透传与本身无关的 props)
  • ^ A ` – Q T大化可组合性
  • 包装显现称号以便轻松调试

留意事项

  • 不要在 rend= 1 : O x beO N E F x e s Gr 办法中运用 HOC

Reactdiff 算法(称为协u b H o ,调)运用组件标识来确认它是应该更新现有子树仍是将其丢弃并挂载新子树。 假如从 render 回来的组件与前一个烘托中的组件相同(===),则 React 经过将子树与新子O n ^ G ~树进行区分来递归更新子树。 假如X x F _ i 0它们不相等,则彻W _ h } ;底卸载前一个子树。

这不仅仅是功能问题 —— 从头挂载组件会导致该组件及其一切子组件的状态丢失。

假如在组件之外创立 HOC,这样一来组件只会创立一0 P 9次。因而,每次 render 时都会是同S c e P 0 c ] d一个组件。

  • 务必仿制静态办法
  • Refs 不会被传递(需求额外处理)

3. 反向承继

React 官方文档上有这样一段描绘:{ I 8 _ A G HOC 不会修正传入的组件,也不会运用承继来仿制其行为。相反,HOC 经过将组件包装在容器组件中来Z c d f =组成新组件。HOC 是纯函数,没有副作用。

因而呢,我觉得反向承继不是 React 推崇的方式,这儿咱B / I = 9 Z H们能够做一下了解,某些场景下也有或许会用x b ?到。

反向承继
function withColor(WrappedComponent) {
class ProxyComy ) Z ,ponent extends WrappedComponent {
//留意 ProxyComponent 会掩盖 WrappedComponent 的同名函数,包含 state 和 props
render() {
//React.cloneElement(super.render(), { style: { color:'red' }})
return q J | [ 4 * super.ry 0 4 - I ] fender();
}
}
return ProxyComponent;
}

和上一节不同,反向承继不会添加组件的层级,而且也不会有静态特点仿制和 refs 丢失的问题。能够使用它来做烘托绑架,不过我目前没有什么有必要要运用反向承继的场景。

尽管它没有静态特点和 refs的问题,也不会添加层级,可是它也不是那么好% b用,会掩盖同名特点和办法这点就让人很无法。别的尽管能够修正烘托成果,可是不w A M :好注入 props

4. render props

首要, render props 是指一种在 React 组件之间运用一个值为函数的 prop 同享代码的简略技能。

具有 render prop 的组件承受一个函数,该函数回来一个 Reac^ Y q U J Q F It 元素并调用它而不是完成自k ~ c L Y # & /己的烘托逻辑。

<Route
{...rest}
render={routeProps => (
<FadeIn>
<Component {...routeProps} />
</FadeIn>
)}
/>

ReactNative 的开发者,其d % x ~ u C F irender props 的技能运用的许多,例如,Flm _ W n % ! 0atListk [ & = L a - 组件:

import React, {Component} from 'react';
import {
FlatList,
View,
Text,
TouchableHig{ r M , T :hlight
} from 'react-native';
class MyList extends Component {
data = [{ key: 1, title: 'Hello' }, { key: 2, title: 'World' }]
render() {
rP g ) d (eturn (
&$ 9 F  clt;FlatLi] A E $ l Bst
style={{margi6 % 4 ! | XnTop: 60}}
data={this.data}
renderIte_ F : ] G m Pm={({ item, index }) =Z @ 3 I .> {
return (
<TouchableHighlight
onPress={() => { alert(item.title) }}
>
<Textq 1 z Z ( ]>{item.title}</Text>
</TouchableHighlight>
)
}}
ListHeaderComponent={() => {
return (<z ; $ [ 6Text>以下是一个List</Text>)
}}
ListFooterComponent={() => {
return <Text>没有更多数据</Text>
}}
/>
)
}
}

例如: FlatListrenderItemListHeaderComponent 便是render prop

留意,render prop 是由于方式才被称为 render prop ,你不一定要用L i , x h o `名为 renderprop 来运用这种方} T y 8 j ) [式。render prop 是一G ~ j m 1 ?个用于告知组件需求烘托什么内容的函数 prop

其实,咱们在封装组件的时分,也经常会使用到这个技能,p y ( /例如咱们封装一个轮播图组件D T *,可是每个页c b K C %面的款式是不一致的,咱们能够供给一个根底款式,可是也f { v Z F要答应自/ U C Z W $ d界说,不然就没有通用价值了:

/8 D C (/供给一个 renderPage 的 pw j 4 W j 6 Z @ }rop
class Swiper extends React.Pur^ 0 G }eComponent {
getPages() {
if(typeof renderPage === 'function') {
return this.props.renderPage(XX,XXX)
}
}
render() {
const pages = typeof renderPage === 'function' ? this.props.rendeB E , = D ~rPage(XX,XXX) : XXX O | 2 w iXX;
return (
<View>
<Animated.Vr n ^iew>
{pages}
<% G V ` : ] P;/Anr K a k b 3 vimated.View&gc | E 4 P E x [ %t;
</View>
)
}
}

留意事项

Render PropsReact.Pu1 ~ }reCompone6 Q L o u i B 4nt 一起运用时要小心

假如在 render 办法里创立函数,那么 render pr} + } G c V $ V 4ops,会抵消运用 React.PureComponent 带来的优势。由于浅比较 props 的时分总会得到 false,而且在这种情况下每一个 r2 G = ^ ^ Zender 对于 render prop 将会生成一个新的值。

import Rl * & * Deact from 'react';
import { View } from 'react-native';
import Swiper from 'XXX';
classA i ) U : 7 | : W MySwiper extends React.CoC b pmponent {; ` j k n i 1
renD O Kder()P p I P ^ j : {
return (
<Swiper
renderPage={r [ $ U 9 f ^ K(pageDate, pageIndex) => {
return (
<View></View>
)
}}
/>
)
}
}

这儿应该比较好了解,这样写,renderPage 每次都会生成一个新的值,许d . X q j zReact 功能优化上也会提及到这R v E一点。咱们能够将 renderPage 的函数1 0 ( ~ p ! ! H M界说为实例办法,S [ S , = ] 如下:

import React from 'react';
import { View } from 'react-native';
import Swiper from 'XXX';
class Mt Y 7 t P PySwiper extends React.Component {
renderPage(pageDate, pageIndex) {
return (
<ViewJ K C><^ F ,/View>
)
}
render() {
return (
<Swiper
renderPage={this.renderPage}
/>
)
}
}

假如你无法静态界说 prop,则 <Swiper> 应该扩展 R l ` Z Q j Leact.Component,由于也没有浅比较的必要了,就不要浪费时刻去比较了。

5. Hooks

HookReact 16.8 的新增特性,它能够让你在不编写 class 的情况下运用 state 以及其他的 React 特性。HOCrender props 尽管都能够

React 现已内置了一些 Hooks,如: useStateuseEffectuseContextuseReduJ 0 , L 7 Q = 5cerG N Z #useCallbackuseMemouseRefHook,假如你还不清楚这些 Hook,那么能够优先阅览一下官方文档。

咱们主要是将怎么使用 Hooks 来进行组件逻辑复用。假定,咱们有这样一个需求,在开发环境下,每次烘托时,打印出组件的 props

import React, {useEffect} fromh & r 4 $ 2 X / 'react';
export default function useLogger(componentName,...i K V n e o X Dparams) {
useEffect(() => {
if(proces0 T W - M ! Xs.env.NODE_ENV === 'development') {J ~ U 5 R P
console.0 U p c dlog+ + D q ? A(componentNO K F j p  D 1ame, ...params);
}
});
}

运用时:

import React, { useState } from 'react';
import useLogger from './useLogger';
export default function Counter(props) {
let [count, setCount] = useState(0);
useLogger('Counter', prop/ Q s _ Q ` S is);
return (
<div>
<butl R q % R -ton onClick={() => setC- l iount(count + 1)}>+</button>
<p>{`${props.titl# A & 8 Oe}, ${count}`}</p>
</div>
)
}

别的,官方文档自界说I C x i | Hook. P L $ d z l节也一步一步演示了怎么使用 HookQ B u r K q T e 来进行逻辑复用。我由于版别约束,还没有在项目中使用 Hook ,尽管文档现已看过屡次。读到这儿,一般都t s ? [ l L q会有一个疑问,那便是 Hook 是否会替代 render propsHOC,关于这一点,官方也给出了答案:

通常,render props 和高阶组件只烘托一个子节点。咱们U ` F F 9以为让 Hook 来服务这个运用场景愈加简略。e T 8 h m这两种方式仍有用武之地,例如,FlatList 组件的 renderItem 等特点,或许是6 – 4 K V B [ , 一个可见的容器组件或许会有它自己的 DOM 结构。但在大部分场景下,* [ O ) EHook 足够了,而且能够协助削减嵌套。

HOC 最最Z ~ 9 4 f D ; ! *最讨厌的一点便是层级嵌套了,假如项目是根据新版别进行开发,那么需求p X 0 2逻辑复用时,优先考虑 Hook,假如无法完成需求,那么再运用 render propsHOC 来解决。

参阅链接

  • Mixins Considered Harmful
  • 高阶组件
  • 自界说Hook
  • Hooks FAQ

最后,假如便利的话,点个Star鼓舞: github.com/YvetteLau/B…

React组件逻辑复用的那些事儿(Mixins -> HOC+render props -> Hooks)

发表评论

提供最优质的资源集合

立即查看 了解详情