recompacting your react application

Post on 15-Apr-2017

103 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Greg Bergé, @neoziro

Recompacting your React application

• From mixins to higher-order components

• Higher-order components

• Debugging & performances

From mixins to higher-order components

React v0.3.0 First public release of React,

with mixins support.

29/03/2013

🗓

– from React documentation

“Sometimes very different components may share some common functionality. These are sometimes called cross-cutting concerns.”

React.createClass({ handleClick: function (event) { event.preventDefault(); window.history.back(); } render: function () { return <a onClick={this.handleClick}>Back</a>; }});

I want to reuse the back logic :)

Let’s create a mixin!

const BackMixin = { handleClick: function (event) { event.preventDefault(); window.history.back(); }};

React.createClass({ mixins: [BackMixin], render: function() { return <a onClick={this.handleClick}>Back</a>; }});

Let’s put the rendering logic inside!

const BackMixin = { handleClick: function (event) { event.preventDefault(); window.history.back(); }

renderBackButton: function () { return <a onClick={this.handleClick}>Back</a>; }};

React.createClass({ mixins: [BackMixin], render: function() { return this.renderBackButton(); }});

And now in real life…

React.createClass({ mixins: [BackMixin, RouterMixin],

handleClick: function (event) { event.preventDefault(); this.transitionTo('home'); } render: function() { return ( <div> <a onClick={this.handleClick}>Go home</a> {this.renderBackButton()} </div> ); }});

OK let’s refactor it.

😰

const BackMixin = { handleBack: function (event) { event.preventDefault(); window.history.back(); }

renderBackButton: function () { return <a onClick={this.handleBack}>Back</a>; }};

A wild component appears!

React.createClass({ mixins: [BackMixin, PureRenderMixin, RouterMixin, ForwardMixin],

render: function() { return ( <div> <a onClick={this.handleClick}>Go back</a> </div> ); }});

I forgot this one

Mixins

Name clashingHard refactoring

Complexity

😫

10/03/2015React v0.13.0

Support for using ES6 classes to build React components.

🗓

class extends React.Component { handleClick = (event) => { event.preventDefault(); window.history.back(); }; render() { return <a onClick={this.handleClick}>Back</a>; }}

No mixin support.

Let’s try inheritance!

💡

class BackComponent extends React.Component { handleClick = (event) => { event.preventDefault(); window.history.back(); };}

class extends BackComponent { render() { return <a onClick={this.handleClick}>Back</a>; }}

I want it to be pure!

class BackComponent extends React.PureComponent { handleClick = (event) => { event.preventDefault(); window.history.back(); };}

class extends BackComponent { render() { return <a onClick={this.handleClick}>Back</a>; }}

Not every time…

We don’t need a hierarchy, we need a composability.

😰

React Blog post Mixins Considered harmful

by Dan Abramov

13/07/2016

🗓

For the first time, “higher-order components”

concept is mentioned on React website.

😳

What is a higher-order component?

Component => EnhancedComponent

Do you know Redux?

connect(state => state)(TodoApp)

Higher-order components step by step

class extends Component { state = { value: '' };

handleChange = ({ target: { value } }) => this.setState({ value }); render() { return ( <input onChange={this.handleChange} value={this.state.value} /> ) }}

class extends Component { state = { value: '' };

handleChange = ({ target: { value } }) => this.setState({ value }); render() { return ( <input onChange={this.handleChange} value={this.state.value} /> ) }}

Model

Controller

View

1. The View

const View = ({ onChange, value }) => <input onChange={onChange} value={value} />

const View = ({ onChange, value }) => <input onChange={onChange} value={value} />

const View = 'input'

2. The Model

const Model = class extends Component { state = { value: '' }

handleChange = value => this.setState({ value })

render() { return ( <View onChange={({ target: { value } }) => handleChange(value)} value={this.state.value} /> ) }}

My model is not generic

const model = BaseComponent => class extends Component { state = { value: '' }

handleChange = value => this.setState({ value })

render() { return ( <BaseComponent {...this.props} onChange={this.handleChange} value={this.state.value} /> ) }}

More generic?

const withState = (stateName, handlerName, initialValue) => BaseComponent => class extends Component { state = { [stateName]: initialValue }

handleChange = value => this.setState({ [stateName]: value })

render() { return ( <BaseComponent {...this.props} {...{ [stateName]: this.state[stateName], [handlerName]: this.handleChange, }} /> ) } }

const model = withState('value', 'onChange', '')

Recomp(act|ose)

const model = recompact.withState('value', 'onChange', ‘')

const model = recompact.withState('value', 'onChange', ‘')

const MyInput = model('input')

😒

2. The Controller

const controller = BaseComponent => class extends Component { handleChange = ({ target: { value } }) => this.props.onChange(value); render() { return ( <BaseComponent {...this.props} onChange={handleChange} /> ) } }

More generic?

const withHandlers = config => BaseComponent => class extends Component { constructor(props) { super(props) this.handlers = {} Object.keys(config).forEach((key) => { this.handlers[key] = (...args) => config[key](this.props)(...args); }) } render() { return ( <BaseComponent {...this.props} {...this.handlers} /> ) } }

const controller = withHandlers({ onChange: ({ onChange }) => ({ target: { value }}) => onChange(value),})

const controller = recompact.withHandlers({ onChange: ({ onChange }) => ({ target: { value }}) => onChange(value),})

const MyInput = recompact.compose( recompact.withState('value', 'onChange', ''), recompact.withHandlers({ onChange: ({ onChange }) => ({ target: { value }}) => onChange(value), }),)('input')

class extends Component { state = { value: '' };

handleChange = ({ target: { value } }) => this.setState({ value }); render() { return ( <input onChange={this.handleChange} value={this.state.value} /> ) }}

OK, you won 3 lines…

const MyBluePerfInput = recompact.compose( // Performance recompact.pure, // Model recompact.withState('value', 'onChange', ''), // Controller recompact.withHandlers({ onChange: ({ onChange }) => ({ target: { value } }) => onChange(value), }), // Style recompact.withProps({ style: { color: ‘blue’ } }),)('input')

Debugging & performances

Recompose

isReferentiallyTransparentFunctionComponent

What is a referentially transparent component?

• Function

• No default props

• No context

const MyBluePerfInput = recompact.compose( // No transparent recompact.pure, // No transparent recompact.withState('value', 'onChange', ''), // No transparent recompact.withHandlers({ onChange: ({ onChange }) => ({ target: { value } }) => onChange(value), }), // Transparent recompact.withProps({ style: { color: ‘blue’ } }),)('input')

Recompact

What are we really doing?

We take some props and return (or not) other props.

(next, props) => next(props)

subscribe, next… it reminds me of something

props$ => props$

const mapProps = propsMapper => (next, props) => next(propsMapper(props))

const mapProps = propsMapper => props$ => props$.map(propsMapper)

Higher-order components

Stream of props

Debugging is logging

export default recompact.compose( recompact.withProps({ foo: 'bar' }), recompact.debug(), recompact.renameProp('foo', 'className'), recompact.debug(),)('input')

• Avoid mixins and inheritance

• Separate concerns using higher-order components

• Create small higher-order components and compose them

Thanks!

top related