Лекция 33
npm install recompose
const EnhancedComponent = hoc(BaseComponent)
const composedHoc = compose(hoc1, hoc2, hoc3)
// Same as
const composedHoc = BaseComponent =>
hoc1(
hoc2(
hoc3(
BaseComponent
)
)
)
const enhance = withState(
'counter',
'setCounter',
0
);
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>
)
// трансформировать props
mapProps(
propsMapper: (ownerProps: Object) => Object,
): HigherOrderComponent
// исключить некоторые ключи
const omitProps
= keys =>
mapProps(
props => Object
.keys(props)
.filter(x => keys.every(y => y !== x))
.reduce((r, x) => {
r[x] = props[x];
return r;
}, {})
)
// сливает пропсы с созданными или переданными
withProps(
createProps: (ownerProps: Object) => Object | Object
): HigherOrderComponent
// сливает пропсы с созданными или переданными
// только при изменении ключей или условия
withPropsOnChange(
shouldMapOrKeys: Array<string>
| (props: Object, nextProps: Object) => boolean,
createProps: (ownerProps: Object) => Object
): HigherOrderComponent
// создаёт иммутабельные колбеки в пропсах
withHandlers(
handlerCreators: {
[handlerName: string]: (props: Object) => Function
} |
handlerCreatorsFactory: (initialProps) => {
[handlerName: string]: (props: Object) => Function
}
): HigherOrderComponent
const enhance = compose(
withState('value', 'updateValue', ''),
withHandlers({
onChange: props => event => {
props.updateValue(event.target.value)
},
onSubmit: props => event => {
event.preventDefault()
submitForm(props.value)
}
})
)
const Form = enhance(
({ value, onChange, onSubmit }) =>
<form onSubmit={onSubmit}>
<label>Value
<input
type="text"
value={value}
onChange={onChange}
/>
</label>
</form>
)
// сливает дефолтные пропсы с переданными
defaultProps(
props: Object
): HigherOrderComponent
// переименовывает пропс
renameProp(
oldName: string,
newName: string
): HigherOrderComponent
// переименовывает пропсы
renameProps(
nameMap: { [key: string]: string }
): HigherOrderComponent
// спридит пропс в корень пропсов
flattenProp(
propName: string
): HigherOrderComponent
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' } }
const Post = ({
post: { title, content, author }
}) =>
<article>
<h1>{title}</h1>
<h2>By {author.name}</h2>
<div>{content}</div>
</article>
const enhance = flattenProp('post')
const Post = enhance(({
title, content, author
}) =>
<article>
<h1>{title}</h1>
<h2>By {author.name}</h2>
<div>{content}</div>
</article>
)
// добавляет состояние и метод обновления
withState(
stateName: string,
stateUpdaterName: string,
initialState: any | (props: Object) => any
): HigherOrderComponent
const addCounting = compose(
withState('counter', 'setCounter', 0),
withHandlers({
increment: ({ setCounter }) =>
() => setCounter(n => n + 1),
decrement: ({ setCounter }) =>
() => setCounter(n => n - 1),
reset: ({ setCounter }) =>
() => setCounter(0)
})
)
// добавляет состояние и несколько методов
// для обновления
withStateHandlers(
initialState: Object | (props: Object) => any,
stateUpdaters: {
[key: string]: (state:Object, props:Object)
=> (...payload: any[]) => Object
}
)
const addCounting = withStateHandlers(
({ initialCounter = 0 }) => ({
counter: initialCounter,
}),
{
incrementOn: ({ counter }) => (value) => ({
counter: counter + value,
}),
decrementOn: ({ counter }) => (value) => ({
counter: counter - value,
}),
resetCounter: (_, { initialCounter = 0 }) => () => ({
counter: initialCounter,
}),
}
)
// добавляет состояние
// метод для диспетчеризации
// и обновление через редьюсер
withReducer<S, A>(
stateName: string,
dispatchName: string,
reducer: (state: S, action: A) => S,
initialState: S | (ownerProps: Object) => S
): HigherOrderComponent
// условный рендеринг
branch(
test: (props: Object) => boolean,
left: HigherOrderComponent,
right: ?HigherOrderComponent
): HigherOrderComponent
// превращение в HOC
renderComponent(
Component: ReactClass | ReactFunctionalComponent | string
): HigherOrderComponent
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>
)
// всегда рендерит null
renderNothing: HigherOrderComponent
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>
)
// аналог shouldComponentUpdate
shouldUpdate(
test: (props: Object, nextProps: Object) => boolean
): HigherOrderComponent
// аналог PureComponent
pure: HigherOrderComponent
// ре-рендер только если изменились
// значения указанных ключей
onlyUpdateForKeys(
propKeys: Array<string>
): HigherOrderComponent
// навешивание обработчиков ЖЦ
lifecycle(
spec: Object,
): HigherOrderComponent
const PostsList = ({ posts }) => (
<ul>{posts.map(p => <li>{p.title}</li>)}</ul>
)
const PostsListWithData = lifecycle({
componentDidMount() {
fetchPosts().then(posts => {
this.setState({ posts });
})
}
})(PostsList);
// оборачивает функциональный компонент
// в класс
toClass: HigherOrderComponent
// создаёт компонент
// к его children будет применён HOC
withRenderProps(
hoc: HigherOrderComponent
): ReactFunctionalComponent
const enhance = withProps(
({ foo }) => ({ fooPlusOne: foo + 1 })
)
const Enhanced = withRenderProps(enhance)
<Enhanced foo={1}>
{({ fooPlusOne }) => <h1>{fooPlusOne}</h1>}
</Enhanced>
// renders <h1>2</h1>
// делает композицию HOC's
// справа налево
compose(...functions: Array<Function>): Function
// принимает компонент в пропсах
// рендерит его с остальными пропсами
componentFromProp(propName: string): ReactFunctionalComponent
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" />
// композиция компонентов через вложенность
nest(
...Components: Array
<ReactClass
| ReactFunctionalComponent
| string
>
): ReactClass
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>