Recompose

Лекция 33

Recompose

Recompose - Install


npm install recompose
    

Recompose - HOC


const EnhancedComponent = hoc(BaseComponent)
    

Recompose - HOC's composition


const composedHoc = compose(hoc1, hoc2, hoc3)

// Same as
const composedHoc = BaseComponent =>
  hoc1(
    hoc2(
      hoc3(
        BaseComponent
      )
    )
  )
    

Recompose - Wrap state


const enhance = withState(
  'counter',
  'setCounter',
  0
);
    

Recompose - Wrap state


const Counter = enhance(
  ({ counter, setCounter }) =>
    <div>
      Count: {counter}

      <button
        onClick={() => setCounter(n => n + 1)}
      >
        Increment
      </button>
      <button
        onClick={() => setCounter(n => n - 1)}
      >
        Decrement
      </button>
    </div>
)
    

Recompose - mapProps


// трансформировать props
mapProps(
  propsMapper: (ownerProps: Object) => Object,
): HigherOrderComponent
    

Recompose - mapProps


// исключить некоторые ключи
const omitProps
  = keys =>
      mapProps(
        props => Object
          .keys(props)
          .filter(x => keys.every(y => y !== x))
          .reduce((r, x) => {
            r[x] = props[x];
            return r;
          }, {})
      )
    

Recompose - withProps


// сливает пропсы с созданными или переданными
withProps(
  createProps: (ownerProps: Object) => Object | Object
): HigherOrderComponent
    

Recompose - withPropsOnChange


// сливает пропсы с созданными или переданными
// только при изменении ключей или условия
withPropsOnChange(
  shouldMapOrKeys: Array<string>
    | (props: Object, nextProps: Object) => boolean,

  createProps: (ownerProps: Object) => Object
): HigherOrderComponent
    

Recompose - withHandlers


// создаёт иммутабельные колбеки в пропсах
withHandlers(
  handlerCreators: {
    [handlerName: string]: (props: Object) => Function
  } |
  handlerCreatorsFactory: (initialProps) => {
    [handlerName: string]: (props: Object) => Function
  }
): HigherOrderComponent
    

Recompose - withHandlers


const enhance = compose(
  withState('value', 'updateValue', ''),
  withHandlers({
    onChange: props => event => {
      props.updateValue(event.target.value)
    },
    onSubmit: props => event => {
      event.preventDefault()
      submitForm(props.value)
    }
  })
)
    

Recompose - withHandlers


const Form = enhance(
  ({ value, onChange, onSubmit }) =>
    <form onSubmit={onSubmit}>
      <label>Value
        <input
          type="text"
          value={value}
          onChange={onChange}
        />
      </label>
    </form>
)
    

Recompose - defaultProps


// сливает дефолтные пропсы с переданными
defaultProps(
  props: Object
): HigherOrderComponent
    

Recompose - renameProp


// переименовывает пропс
renameProp(
  oldName: string,
  newName: string
): HigherOrderComponent
    

Recompose - renameProps


// переименовывает пропсы
renameProps(
  nameMap: { [key: string]: string }
): HigherOrderComponent
    

Recompose - flattenProp


// спридит пропс в корень пропсов
flattenProp(
  propName: string
): HigherOrderComponent
    

Recompose - flattenProp


const enhance = compose(
  withProps({
    object: { a: 'a', b: 'b' },
    c: 'c'
  }),
  flattenProp('object')
)

const Abc = enhance(BaseComponent)

// Base component receives props:
// { a: 'a', b: 'b', c: 'c', object: { a: 'a', b: 'b' } }
    

Recompose - flattenProp


const Post = ({
  post: { title, content, author }
}) =>
  <article>
    <h1>{title}</h1>
    <h2>By {author.name}</h2>
    <div>{content}</div>
  </article>
    

Recompose - flattenProp


const enhance = flattenProp('post')

const Post = enhance(({
  title, content, author
}) =>
  <article>
    <h1>{title}</h1>
    <h2>By {author.name}</h2>
    <div>{content}</div>
  </article>
)
    

Recompose - withState


// добавляет состояние и метод обновления
withState(
  stateName: string,
  stateUpdaterName: string,
  initialState: any | (props: Object) => any
): HigherOrderComponent
    

Recompose - withState


const addCounting = compose(
  withState('counter', 'setCounter', 0),

  withHandlers({
    increment: ({ setCounter }) =>
      () => setCounter(n => n + 1),

    decrement: ({ setCounter }) =>
      () => setCounter(n => n - 1),

    reset: ({ setCounter }) =>
      () => setCounter(0)
  })
)
    

Recompose - withStateHandlers


// добавляет состояние и несколько методов
// для обновления
withStateHandlers(
  initialState: Object | (props: Object) => any,
  stateUpdaters: {
    [key: string]: (state:Object, props:Object)
      => (...payload: any[]) => Object
  }
)
    

Recompose - withStateHandlers


const addCounting = withStateHandlers(
  ({ initialCounter = 0 }) => ({
    counter: initialCounter,
  }),
  {
    incrementOn: ({ counter }) => (value) => ({
      counter: counter + value,
    }),
    decrementOn: ({ counter }) => (value) => ({
      counter: counter - value,
    }),
    resetCounter: (_, { initialCounter = 0 }) => () => ({
      counter: initialCounter,
    }),
  }
)
    

Recompose - withReducer


// добавляет состояние
// метод для диспетчеризации
// и обновление через редьюсер
withReducer<S, A>(
  stateName: string,
  dispatchName: string,
  reducer: (state: S, action: A) => S,
  initialState: S | (ownerProps: Object) => S
): HigherOrderComponent
    

Recompose - branch


// условный рендеринг
branch(
  test: (props: Object) => boolean,
  left: HigherOrderComponent,
  right: ?HigherOrderComponent
): HigherOrderComponent
    

Recompose - renderComponent


// превращение в HOC
renderComponent(
  Component: ReactClass | ReactFunctionalComponent | string
): HigherOrderComponent
    

Recompose - renderComponent


const spinnerWhileLoading = isLoading =>
  branch(
    isLoading,
    renderComponent(Spinner)
  )

const enhance = spinnerWhileLoading(
  props => !(props.title && props.content)
)

const Post = enhance(({ title, content }) =>
  <article>
    <h1>{title}</h1>
    <div>{content}</div>
  </article>
)
    

Recompose - renderNothing


// всегда рендерит null
renderNothing: HigherOrderComponent
    

Recompose - renderNothing


const hideIfNoData = hasNoData =>
  branch(
    hasNoData,
    renderNothing
  )

const enhance = hideIfNoData(
  props => !(props.title && props.content)
)

const Post = enhance(({ title, content }) =>
  <article>
    <h1>{title}</h1>
    <div>{content}</div>
  </article>
)
    

Recompose - shouldUpdate


// аналог shouldComponentUpdate
shouldUpdate(
  test: (props: Object, nextProps: Object) => boolean
): HigherOrderComponent
    

Recompose - pure


// аналог PureComponent
pure: HigherOrderComponent
    

Recompose - onlyUpdateForKeys


// ре-рендер только если изменились
// значения указанных ключей
onlyUpdateForKeys(
  propKeys: Array<string>
): HigherOrderComponent
    

Recompose - lifecycle


// навешивание обработчиков ЖЦ
lifecycle(
  spec: Object,
): HigherOrderComponent
    

Recompose - lifecycle


const PostsList = ({ posts }) => (
  <ul>{posts.map(p => <li>{p.title}</li>)}</ul>
)

const PostsListWithData = lifecycle({
  componentDidMount() {
    fetchPosts().then(posts => {
      this.setState({ posts });
    })
  }
})(PostsList);
    

Recompose - toClass


// оборачивает функциональный компонент
// в класс
toClass: HigherOrderComponent
    

Recompose - withRenderProps


// создаёт компонент
// к его children будет применён HOC
withRenderProps(
  hoc: HigherOrderComponent
): ReactFunctionalComponent
    

Recompose - withRenderProps


const enhance = withProps(
  ({ foo }) => ({ fooPlusOne: foo + 1 })
)

const Enhanced = withRenderProps(enhance)

<Enhanced foo={1}>
  {({ fooPlusOne }) => <h1>{fooPlusOne}</h1>}
</Enhanced>
// renders <h1>2</h1>
    

Recompose - compose


// делает композицию HOC's
// справа налево
compose(...functions: Array<Function>): Function
    

Recompose - componentFromProp


// принимает компонент в пропсах
// рендерит его с остальными пропсами
componentFromProp(propName: string): ReactFunctionalComponent
    

Recompose - componentFromProp


const enhance = defaultProps({
  component: 'button'
})

const Button = enhance(
  componentFromProp('component')
)

<Button foo="bar" />
// <button foo="bar" />

<Button component="a" foo="bar" />
// <a foo="bar" />

<Button component={Link} foo="bar" />
// <Link foo="bar" />
    

Recompose - nest


// композиция компонентов через вложенность
nest(
  ...Components: Array
      <ReactClass
      | ReactFunctionalComponent
      | string
      >
): ReactClass
    

Recompose - nest


const ABC = nest(A, B, C)
<ABC pass="through">Child</ABC>

// Effectively the same as
<A pass="through">
  <B pass="through">
    <C pass="through">
      Child
    </C>
  </B>
</A>