ããDXã«åãã¦Recomposeã®HOCãReact Hooksã«ç½®ãæãã
ã©ããTAKUYAã§ããInkdropã¨ããMarkdownãã¼ãã¢ããªã1人ã§ä½ã£ã¦ãã¾ãããã®ã¢ãã¤ã«çã¯React Nativeã§çµã¾ãã¦ãã¾ããæè¿ã³ã¼ããã¼ã¹ããªãã¡ã¯ã¿ãªã³ã°ãã¦ãRecomposeããReact Hooksã«ä¹ãæãã¾ãããæ¬ç¨¿ã§ã¯ãã®ä½æ¥ã®éã«çºè¦ããã³ããªã©ãã·ã§ã¢ãããã¨æãã¾ãã
HOCå¤ç¨ã¯ã¡ã³ããã³ã¹æ§ãä½ããªã
Recomposeã¨ã¯HOC(Higher-order components)ã§å¹çããStateless functional componentsãã¼ã¹ã®Reactã¢ããªãçµãããã®ä¾¿å©é¢æ°ã©ã¤ãã©ãªã§ããä½è ã¯Andrew Clarkã§ããä¾ãããªããReactçLodashã¿ãããªæãã§ãããã®ã©ã¤ãã©ãªã®éçºã¯2018å¹´12æ4æ¥ãæå¾ã«æ¢ã¾ã£ã¦ãã¾ãããªããªãå½¼ãReactã®ãã¼ã ã«åå ããããã§ããããã¦React HooksãReact v16.8ã«ã¦å°å ¥ããã¾ããã
å½¼ããè¨ãããã«ãReact Hooksã使ãããã«ããããå¤ãå¾¹ãã¦æ¢åã³ã¼ããæ¸ãç´ãå¿ è¦ã¯ããã¾ãããã§ãåã¯ããã¾ãããèªåã®ã³ã³ãã¼ãã³ã群ãæ¸ãç´ããçç±ã¯ããã¤ãããã¾ããRecomposeã¯HOCãã¼ã¹ã§ã¢ããªãæ¸ãããã®ã©ã¤ãã©ãªã§ããHOCã®é¢æ°ç¾¤ã¯åå©ç¨æ§ãé«ããããåããã¸ãã¯ãæ¸ãæéã大ããçãã¦ããã¾ããä¸æ¹ã§ããã®Recomposeã¨HOCãæ¡ç¨ãããã¨ã«ãããã¡ãªããããããã¨ãåããã¾ããï¼
1. ã¹ã¿ãã£ãã¯ã¿ã¤ãã³ã°ãä¸æãåããªã
åã¯ããã¸ã§ã¯ãã®ã¿ã¤ããã§ãã¯ã«Flowã使ã£ã¦ãã¾ããRecomposeã®Flowå®ç¾©ã¯æ¬¡ã®ä¾ã®ããã«ãFlowã®Type Inferenceæ©è½ã«å¼·ãä¾åãã¦ãã¾ãï¼
import { compose, withHandlers, pure, type HOC } from 'recompose'
type Props = {}const enhance: HOC<*, Props> = compose(
connect(({ editingNote, editor, session }) => ({
editingNote,
readOnly: editor.readOnly || session.isReadOnly
})),
pure,
withKeyboard(),
withHandlers({
handleTitleFocus: _props => () => {}
})
)const Editor = enhance(props => {
// ...
})
ããã ã¨ãªãã props
ãé »ç¹ã« any
ã¨ãã¦è§£æ±ºããã¦ãã¾ã£ã¦ãã³ã³ãã¼ãã³ãä¸ã®ã¿ã¤ããã§ãã¯ãä¸æãåããªãäºããã£ã¦å°ã£ã¦ãã¾ãããFlowã¯åãééã£ãããããã£ãåç
§ãããã¨ãã¦ãã¦ãæãã¦ããã¾ãããããã§ã¯æå³ããªããããã«ã props
ã®ä¸èº«ãã³ã¼ããè¦ãã ãã§ã¯ããã«åãããªãã¨ããåé¡ãããã¾ããHOCã®å
¥ãåãå¢ããã°å¢ããã»ã©ããã®åé¡ã¯æ·±å»åãã¾ãã
2. React Developer Toolsã§ãããã°ãã¥ãã
ä¸è¨ç»åãã覧ã®éããããããã®pure(withHandlers(Component))
ã®ãããªHOCãç©ã¿éãªã£ã¦ãããå®éã®ã³ã³ãã¼ãã³ãæ§é ãå
¨ãææ¡ã§ãã¾ããããã®ããã§React Developer Toolsã®å®ç¨æ§ãã»ã¨ãã©å¤±ããã¦ãã¾ã£ã¦ãã¾ãã
3. fbjsã«ä¾åãã¦ãã
Recomposeã¯fbjsã«ä¾åãã¦ãã¾ã â â ããã¯Facebookãå é¨ã§ä½¿ã£ã¦ãã便å©ã©ã¤ãã©ãªã§ãããä»ã¯å»æ¢ããã¦ã¡ã³ãããã¦ãã¾ããããã®ã©ã¤ãã©ãªãæ´ã«å¤ãã¢ã¸ã¥ã¼ã«ã«ä¾åãã¦ããããã使ãã ãã§ä¸å¿ è¦ã«ããã¸ã§ã¯ãã¯è¥å¤§åãèå¼±ã«ãªã£ã¦ãã¾ãã¾ãããªã®ã§ãåã¯ãã¤ãfbjsãä¾åããªã¼ã«å«ã¾ãã¦ããªãã確èªãã¦ã¯é¤å»ããããã«åªãã¦ãã¾ãã
æ¢ã«ãã¹ã¯ãããçã«ã¦React Hooksãæ¡ç¨ãã¦ããããããä¸è¨ã§è°è«ããåé¡ãä¸æã解決ãã¦ããããã¨ãå®æãã¦ãã¾ãããHooksã«ãã£ã¦ããå¿«é©ãªDX(Development Experience)ãå¾ããããã¨ãåããã¾ãããHooksã¯ã³ã¼ããã¼ã¹ãè¦éããããã¦ããã¾ããã°ãã¸ã§ããAndrew Clarkã
ã§ã¯ã以éããå®éã«ã©ã®ããã«RecomposeãReact Hooksã«ç½®ãæããã®ããç´¹ä»ãã¾ããå®éã¯æ³åããããã¨ã¦ãç°¡åã§ãããããã¯æ°ããããã¸ã§ã¯ãã§Hooksã®ä½¿ãæ¹ãå¦ã¶éã«ãåèã«ãªããã¨æãã¾ãã
ããããDXã«åãã¦ã³ã¼ãã綺éºã«ããã
以ä¸ã¯Recomposeã®åã¦ã¼ã¹ã±ã¼ã¹ã«å¯¾ããã³ã¼ãã®ããã©ã¼ã¢ãã¿ã¼ã§ãã
Recompose.lifecycle -> React.useEffect
Before:
const PostsList = ({ posts }) => (
<ul>{posts.map(p => <li>{p.title}</li>)}</ul>
)const PostsListWithData = lifecycle({
componentDidMount() {
fetchPosts().then(posts => {
this.setState({ posts });
})
}
})(PostsList);
After:
import { useEffect, useState } from 'react'const PostsList = () => {
const [posts, setPosts] = useState([]);
useEffect(() => {
fetchPosts().then(posts => {
setPosts(posts);
})
}, [])return (
<ul>{posts.map(p => <li>{p.title}</li>)}</ul>
)
}
Recompose.withHandlers -> useCallback
Before:
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>
)
After:
import { useState, useCallback } from 'react'const Form = () => {
const [value, updateValue] = useState('')
const onChange = useCallback(event => {
updateValue(event.target.value)
}, [updateValue])
const onSubmit = useCallback(event => {
event.preventDefault()
submitForm(value)
}, [value])return (
<form onSubmit={onSubmit}>
<label>Value
<input type="text" value={value} onChange={onChange} />
</label>
</form>
)
}
Recompose.withStateHandlers -> React.useState
Before:
const Counter = withStateHandlers(
({ initialCounter = 0 }) => ({
counter: initialCounter,
}),
{
incrementOn: ({ counter }) => (value) => ({
counter: counter + value,
}),
decrementOn: ({ counter }) => (value) => ({
counter: counter - value,
}),
resetCounter: (_, { initialCounter = 0 }) => () => ({
counter: initialCounter,
}),
}
)(
({ counter, incrementOn, decrementOn, resetCounter }) =>
<div>
<Button onClick={() => incrementOn(2)}>Inc</Button>
<Button onClick={() => decrementOn(3)}>Dec</Button>
<Button onClick={resetCounter}>Reset</Button>
</div>
)
After:
import { useState, useCallback } from 'react'const Counter = () => {
const [counter, setCounter] = useState(0)
const incrementOn = useCallback((value) => {
setCounter(counter + value)
}, [counter])
const decrementOn = useCallback((value) => {
setCounter(counter - value)
}, [counter])
const resetCounter = useCallback(() => {
setCounter(0)
}, [counter])return (
<div>
<Button onClick={useCallback(() => incrementOn(2), [counter])}>Inc</Button>
<Button onClick={useCallback(() => decrementOn(3), [counter])}>Dec</Button>
<Button onClick={resetCounter}>Reset</Button>
</div>
)
}
Recompose.pure -> React.memo
Before:
const Comp = pure(props => <div>{props.message}</div>)
After:
const Comp = memo(props => <div>{props.message}</div>)
Recompose.onlyUpdateForKeys -> React.memo
Before:
const enhance = onlyUpdateForKeys(['title', 'content', 'author'])
const Post = enhance(({ title, content, author }) =>
<article>
<h1>{title}</h1>
<h2>By {author.name}</h2>
<div>{content}</div>
</article>
)
After:
import { memo } from 'react'const Post = memo(({ title, content, author }) => {
return (
<article>
<h1>{title}</h1>
<h2>By {author.name}</h2>
<div>{content}</div>
</article>
)
}, (prevProps, nextProps) => {
return prevProps.title === nextProps.title &&
prevProps.content === nextProps.content &&
prevProps.author === nextProps.author
})
From the doc: You can also add a second argument to specify a custom comparison function that takes the old and new props. If it returns true, the update is skipped.
è¨³ï¼ æ°æ§ã®propsãæ¯ã¹ãé¢æ°ãäºã¤ç®ã®å¼æ°ã§æå®ã§ãã¾ããããtrueãè¿ãã°ãã³ã³ãã¼ãã³ãã®æ´æ°ã¯ã¹ãããããã¾ãã
ãæ°ã¥ãããããã¾ããããReact Hooksã«ç½®ãæããããã¨ãã£ã¦ã³ã¼ãéãããã£ã¨æ¸ããã¨ã¯ããã¾ããããªããªãRecomposeã§æ¢ã«ååå¹ççã«æ¸ããããã§ãããã¦ããã®ãªãã¡ã¯ã¿ãªã³ã°ã«ãã£ã¦Reactã³ã³ãã¼ãã³ãã®æ§é ãDeveloper Toolsä¸ã§ããªãè¦éãè¯ããªãã¾ããï¼
ãã®ãªãã¡ã¯ã¿ãªã³ã°ä½æ¥ã«ãã£ã¦ãFlowãæ£ããåä½ããããã«ãªããããã¤ãã®ä¸è¦ãªã³ã¼ããé¤å»ãããã¨ãåºæ¥ã¾ããã
æ¬ç¨¿ãRecomposeãã¼ã¹ã®ããã¸ã§ã¯ãããæã¡ã®æ¹ã®åèã«ãªããã¨ãç¥ã£ã¦ãã¾ã :)