|
import React from 'react'; |
|
import PropTypes from 'prop-types'; |
|
import TestRenderer from 'react-test-renderer'; |
|
|
|
const Benchmark = require('benchmark'); |
|
|
|
Benchmark.options.minSamples = 1000; |
|
|
|
const suite = new Benchmark.Suite(); |
|
|
|
// -------------------------------------------------- |
|
// PROPS |
|
// -------------------------------------------------- |
|
|
|
const outerProps = { |
|
style: { |
|
color: 'black', |
|
}, |
|
id: 'outer', |
|
}; |
|
|
|
const innerProps = { |
|
id: 'inner', |
|
}; |
|
|
|
// -------------------------------------------------- |
|
// PRESENTATIONS |
|
// -------------------------------------------------- |
|
|
|
const PresentationCloneElement = ({ outerElement, innerElement }) => ( |
|
<div> |
|
{React.cloneElement( |
|
outerElement, |
|
outerProps, |
|
React.cloneElement(innerElement, innerProps), |
|
)} |
|
</div> |
|
); |
|
|
|
PresentationCloneElement.propTypes = { |
|
outerElement: PropTypes.node.isRequired, |
|
innerElement: PropTypes.node.isRequired, |
|
}; |
|
|
|
const PresentationRenderProp = ({ outerRender, innerRender }) => ( |
|
<div> |
|
{outerRender({ |
|
...outerProps, |
|
children: innerRender(innerProps), |
|
})} |
|
</div> |
|
); |
|
|
|
PresentationRenderProp.propTypes = { |
|
outerRender: PropTypes.func.isRequired, |
|
innerRender: PropTypes.func.isRequired, |
|
}; |
|
|
|
// -------------------------------------------------- |
|
// CONTAINERS |
|
// -------------------------------------------------- |
|
|
|
const ContainerCloneElement = ({ isDisabled, label }) => ( |
|
<PresentationCloneElement |
|
outerElement={<button disabled={isDisabled} />} |
|
innerElement={<span>{label}</span>} |
|
/> |
|
); |
|
|
|
ContainerCloneElement.propTypes = { |
|
isDisabled: PropTypes.bool.isRequired, |
|
label: PropTypes.string.isRequired, |
|
}; |
|
|
|
const ContainerRenderProp = ({ isDisabled, label }) => ( |
|
<PresentationRenderProp |
|
outerRender={({ children, ...props }) => ( |
|
<button disabled={isDisabled} {...props}> |
|
{children} |
|
</button> |
|
)} |
|
innerRender={props => <span {...props}>{label}</span>} |
|
/> |
|
); |
|
|
|
ContainerRenderProp.propTypes = { |
|
isDisabled: PropTypes.bool.isRequired, |
|
label: PropTypes.string.isRequired, |
|
}; |
|
|
|
// -------------------------------------------------- |
|
// TESTS |
|
// -------------------------------------------------- |
|
|
|
const testCloneElement = () => |
|
TestRenderer.create(<ContainerCloneElement isDisabled label="Yoyo" />); |
|
|
|
const testRenderProp = () => |
|
TestRenderer.create(<ContainerRenderProp isDisabled label="Yoyo" />); |
|
|
|
// these should have identical output: |
|
console.log(testCloneElement().toJSON()); |
|
console.log(testRenderProp().toJSON()); |
|
|
|
// -------------------------------------------------- |
|
// SUITE |
|
// -------------------------------------------------- |
|
|
|
suite |
|
.add('React.cloneElement', () => { |
|
testCloneElement(); |
|
}) |
|
.add('Render Prop', () => { |
|
testRenderProp(); |
|
}) |
|
// add listeners |
|
.on('cycle', event => { |
|
console.log(String(event.target)); |
|
}) |
|
.on( |
|
'complete', |
|
// eslint-disable-next-line func-names |
|
function() { |
|
console.log(`Fastest is ${this.filter('fastest').map('name')}`); |
|
}, |
|
) |
|
// run async |
|
.run({ async: true }); |
Correction:
The "Render Prop"'s "nesting" problem can actually be avoided:
But needing to manually spread props is still an issue, but it does offer the flexibility of being able to render a deeper structure, and pepper the props at different levels.
So all-in-all, with that strong benefit, and the new legibility of "Render Prop", I'd propose going with that pattern...!