Patterns
HOC
Higher Order Components.
Solve:
- Reuse code with using ES6 classes.
- Compose multiple HOCs.
Upside:
- Reusable (abstract same logic).
- HOC is flexible with input data (pass input data as parameters or derive it from props).
Downside:
- Wrapper hell:
withA(withB(withC(withD(Comp)))). - Implicit dependencies: which HOC providing a certain prop.
- Name collision/overlap props: overwrite the same name prop silently.
- HOC is not flexible with output data (to WrappedComponent).
// ToggleableMenu.jsx
function withToggleable(Clickable) {
return class Toggleable extends React.Component<{ children: ReactElement }> {
constructor() {
super()
this.toggle = this.toggle.bind(this)
this.state = { show: false }
}
toggle() {
this.setState(prevState => ({ show: !prevState.show }))
}
render() {
return (
<div>
<Clickable {...this.props} onClick={this.toggle} />
{this.state.show && this.props.children}
</div>
)
}
}
}
class NormalMenu extends React.Component<{ onClick: Function, title: string }> {
render() {
return (
<div onClick={this.props.onClick}>
<h1>{this.props.title}</h1>
</div>
)
}
}
const ToggleableMenu = withToggleable(NormalMenu)
export default ToggleableMenu
class Menu extends React.Component {
render() {
return (
<div>
<ToggleableMenu title="First Menu">
<p>Some content</p>
</ToggleableMenu>
<ToggleableMenu title="Second Menu">
<p>Another content</p>
</ToggleableMenu>
<ToggleableMenu title="Third Menu">
<p>More content</p>
</ToggleableMenu>
</div>
)
}
}
Render Props
Children/Props as render function:
Solve:
- Reuse code with using ES6 classes.
- Lowest level of indirection.
- No naming collision.
e.g. Context or ThemesProvider is designed base on Render Props.
Upside:
- Separate presentation from logic.
- Extendable.
- Reusable (abstract same logic).
- Render Props is flexible with output data (children parameters definition free).
Downside:
- Wrapper hell (when many cross-cutting concerns are applied to a component).
- Minor memory issues when defining a closure for every render.
- Unable to optimize code with
React.memo/React.PureComponentdue torender()function always changes. - Render Props is not flexible with input data (restricts children components from using the data at outside field).
interface Props {
title: string
children: ReactElement
}
class Toggleable extends React.Component<Props> {
constructor() {
super()
this.toggle = this.toggle.bind(this)
this.state = { show: false }
}
toggle() {
this.setState(prevState => ({ show: !prevState.show }))
}
render() {
return this.props.children(this.state.show, this.toggle)
}
}
export default function ToggleableMenu({ title, children }: Props) {
return (
<Toggleable>
{(show, onClick) => (
<div>
<div onClick={onClick}>
<h1>{title}</h1>
</div>
{show && children}
</div>
)}
</Toggleable>
)
}
class Menu extends React.Component {
render() {
return (
<div>
<ToggleableMenu title="First Menu">
<p>Some content</p>
</ToggleableMenu>
<ToggleableMenu title="Second Menu">
<p>Another content</p>
</ToggleableMenu>
<ToggleableMenu title="Third Menu">
<p>More content</p>
</ToggleableMenu>
</div>
)
}
}
React Hooks
- No wrapper hell: every hook is just one line of code.
- No implicit dependencies: explicit one certain call for one certain hook.
- No name collision and overlap props due to flexible data usage.
- No need for
JSX. - Flexible data usage.
- Flexible optimization methods:
- Avoid re-render with hook deps list.
useMemohook for memorized values.useCallbackhook for memorized functions.useRefhook for lifecycle persistent values.
- Recap related-logic into separate well-structured hooks.
- Reuse same stateful logic with custom hooks.