ããã³ãã¨ã³ãã¨ãã¹ãã¼ããã¼ã ã®@koba04ã§ãã
10/25,26ã®2æ¥éããããã§éå¬ãããReact Conf 2018ã«åå ãã¦æ¥ã¾ããã ä»åã¯ãæ°ã«ãªã£ã¦ãã人ãå¤ãKeynoteã§çºè¡¨ãããHooksã¨Concurrent Reactã«ã¤ãã¦ç´¹ä»ãããã¨æãã¾ãã
ä»åç´¹ä»ãããå 容ã¯ã2014å¹´å¾åãããããReactãè¦ã¦ããä¸ã§ããæã大ããªå¤æ´ã§ããã¨è¨ãã¾ãã
ã«ã³ãã¡ã¬ã³ã¹ã®ãã¼ã¯èªä½ã¯ãã§ã«YouTubeã§å ¬éããã¦ããã®ã§ãå ¨ãã¼ã¯è¦³ããã¨ãåºæ¥ã¾ãã
https://www.youtube.com/playlist?list=PLPxbbTqCLbGE5AihOSExAa4wUM-P42EIJ
Hooks
Hooksã¯ãGitHubä¸ã§ãä¸åå ¬éããã¦ããããReact Conf 2018ã®ã¿ã¤ãã³ã°ã§åãã¦å ¬éãããæ©è½ã§ãã ããã¾ã§ããStateful Function Componentã¨ããæ§æ³ã¯èªããã¦ããã®ã§ããããå®ç¾ããAPIã¨è¨ãã¾ãã
ãã¼ã¯ã®åç»ã¯ä¸è¨ã§ãã ãã¢ä¸»ä½ã§ããããããã®ã§ãæ¯éãã¼ã¯ã観ããã¨ããªã¹ã¹ã¡ãã¾ãã
- React Today and Tomorrow - Sophie Alpert and Dan Abramov
ã¾ãããã¡ãã®ãã¼ã¯ã§ã¯å®éã«Hooksã使ã£ããã¢ãè¡ããã¾ããã
- 90% Cleaner React - Ryan Florence
Hooksã¯ãFunction Componentã«å¯¾ããæ©è½ã§ãã
ç¾æç¹ï¼2018/10/31ï¼ã§ã¯ã¾ã ææ¡æ®µéã®ä»æ§ã§ããã16.7.0-alpha
ã®ãã¼ã¸ã§ã³ã§è©¦ããã¨ãå¯è½ã§ãã
ä¸è¨ã®RFCã®Issueã«ã¯ãã§ã«500件以ä¸ã®ã³ã¡ã³ããããéãã注ç®åº¦ã¨è°è«ãæãAPIã§ãããã¨ããããã¾ãã
https://github.com/reactjs/rfcs/pull/68
ã¾ã RFCã®æ®µéã«ãé¢ããããå ¬å¼ããã¥ã¡ã³ãã«ã8ã¤ã®ã»ã¯ã·ã§ã³ã«æ¸¡ã£ã¦Hooksã®è§£èª¬ãç¨æããã¦ãã¾ãã
https://reactjs.org/docs/hooks-intro.html
ãããã£ã¦ãä»ã®æ®µéã§ããããèªãã§16.7.0-alpha
ã使ããã¨ã§å®éã«è©¦ãã¦ã¿ããã¨ãå¯è½ã§ãã
ã¾ã ææ¡ã®æ®µéã«ãé¢ãããããã¾ã§ä¸å¯§ã«ããã¥ã¡ã³ããç¨æããã¦ããã®ã¯ãHooksã®æå³ãæ£ããä¼ãããã¨ãããã¨ã示ãã¦ããã¨æãã¾ãã
éè¦ãªãã¤ã³ã
ç¾æç¹ã§ã¯ãææ¡æ®µéã®ä»æ§ã§ããå®é¨çã«è©¦ãã¦ãã£ã¼ãããã¯ãããã段éã§ãããã®ãããä»å¾APIãå¤æ´ãããå¯è½æ§ããããããç¾æç¹ã§ãããã¯ã·ã§ã³ç°å¢ã§å©ç¨ãããã¨ã¯æ¨å¥¨ããã¦ãã¾ãã (Facebookã§ã¯ãã§ã«ãããã¯ã·ã§ã³ã§ä½¿ã£ã¦ããããã§ãã)
Hooksãå©ç¨ãããã¨ã§ãããã¾ã§Class Componentã§ããåºæ¥ãªãã£ããã¨ãFunction Componentã§ãå¯è½ã«ãªãã¾ãã ãã ããClass Componentãç¾æç¹ã§ã¯å¼ãç¶ããµãã¼ãããããããæ¢åã®Class ComponentãHooksã使ã£ã¦æ¸ãç´ãå¿ è¦ã¯å°ãªãã¨ãä»ã®æç¹ã§ã¯ããã¾ããã
å°æ¥çã«ã¯ãFunction Componentã ãã«ãªããã¨ãäºæ³ããã¾ãããããã¯è¿ãå°æ¥ã®è©±ã§ã¯ããã¾ããã Facebookã§ã50000以ä¸ã®React Componentãããããããå ¨ã¦ãHooksã使ã£ããã®ã«æ¸ãç´ãäºå®ã¯ãªãã¨è¨ã£ã¦ãã¾ãã
Class Componentã¨Hooksã使ã£ãFunction Componentã¯ä¸ç·ã«ä½¿ããã¨ãå¯è½ãªã®ã§ã æ°ããã³ã¼ãã§ã¯Hooksã使ããªã©ã段éçãªå°å ¥ãå¯è½ã§ãã
Hooksã¨ã¯
Hooksã¨ã¯Function Componentã«å¯¾ãã¦è¿½å ãããæ°ããAPIã§ããç¾ç¶ä¸è¨ã®Hooksã®APIãæä¾ããã¦ãã¾ãã
- Basic Hooks
useState
useEffect
useContext
- Additional Hooks
useReducer
useCallback
useMemo
useRef
useImperativeMethods
useMutationEffect
useLayoutEffect
è²ã ã¨ããã¾ãããã¾ãã¯Basic Hooksã¨ãã¦å®ç¾©ããã¦ãã3ã¤ã®Hooksã ãã§ãç¥ã£ã¦ããã¨ããã¨æãã¾ãã
å
¨ã¦ã®Hooksã¯use
ããå§ã¾ãã¾ãã
ããã¯å¾è¿°ããCustom Hooksãä½æããéã®å½åè¦åã¨ãã¦ãé©ç¨ããã¾ãã
Hooksã®ä½¿ãæ¹
ããã§ã¯ãããããã®Hooksã«ã¤ãã¦ç°¡åã«ä½¿ãæ¹ã示ãã¾ãã
useState
useState
ã¯ãComponentã®Local Stateãå©ç¨ããããã®Hookã§ãã
ä¸è¨ã®ããã«Stateã®åæå¤ã渡ãã¨ãç¾å¨ã®å¤
ã¨Stateãæ´æ°ããããã®é¢æ°
ãé
åã§è¿ãã¾ãã
useState
ã使ããã¨ã§ãClass Componentã使ããã¨ãªãLocal Stateãä½æã§ãã¾ãã
import React, {useState} from 'react'; const Counter = props => { const [count, setCount] = useState(0); return ( <div> <p>count is {count}</p> <button onClick={() => setCount(count + 1)}>++</button> <button onClick={() => setCount(count - 1)}>--</button> </div> ); }
Stateãæ´æ°ããããã®é¢æ°
ã¯ããªãã¸ã§ã¯ãã渡ããå ´åã«ãsetState
ã®ããã«ãã¼ã¸ããã¾ãããç½®ãæãããã¾ãã
useEffect
useEffect
ã¯ãå¯ä½ç¨ã®ããå¦çãå®ç¾©ããHookã§ãã
APIå¼ã³åºããã¤ãã³ãã®è³¼èªã»è§£é¤ãªã©ãcomponentDidMount
ãcomponentDidUpdate
ãªã©ã®ã©ã¤ããµã¤ã¯ã«ã¡ã½ããã§è¡ãªã£ã¦ãããããªå¦çãå®ç¾©åºæ¥ã¾ãã
ãã ããAPIå¼ã³åºãã«å¯¾ãã¦ã¯å¾è¿°ããSuspenseãé©ãã¦ããã±ã¼ã¹ãå¤ãã§ãã
import React, {useEffect} from 'react'; const Header = props => { // textãæ´æ°ããããã³ã«document.titleãæ´æ°ãã useEffect(() => { document.title = props.text; }, [props.text]); return <header>{props.text}</header>; }
useEffect
ã¯ç¬¬ä¸å¼æ°ã«å¯ä½ç¨ã®ããå¦çãå®ç¾©ãã¾ãã
第ä¸å¼æ°ã®ã¿å®ç¾©ããã¨ãFunction Componentã®é¢æ°ãå¼ã°ãã度ã«useEffect
ã«æ¸¡ããã³ã¼ã«ããã¯é¢æ°ãå¼ã°ãã¾ãã
ããã¯ãcomponentDidMount
ã¨componentDidUpdate
ããããã§å¼ã³åºãå ´åã¨åæ§ã¨èãããã¨ãã§ãã¾ãã
useEffect
ã®ç¬¬äºå¼æ°ã«ã¯ãé
åãæå®åºæ¥ã¾ãã
é
åã渡ããå ´åãé
åã®ããããã®è¦ç´ ãå¤æ´ããã¦ããå ´åã®ã¿ã第ä¸å¼æ°ã®ã³ã¼ã«ããã¯é¢æ°ãå¼ã°ãã¾ãã
ã¤ã¾ãä¸è¨ã®å ´åãprops.text
ã®å¤ãå¤ãã£ãå ´åã®ã¿ãdocument.title
ãæ´æ°ããã¾ãã
ããã¯ãcomponentDidUpdate
ã§Propsã®å¤ããã§ãã¯ãã¦å¤æ´ããã£ãå ´åã®ã¿å¦çãè¡ãªã£ã¦ããã±ã¼ã¹ã§å©ç¨ã§ãã¾ãã
useEffect
ã«ç©ºã®é
åã渡ãã¨ã常ã«å¤åããªããã®ã¨ãã¦Componentã®ãã¦ã³ãæã®ã¿ã«ã第ä¸å¼æ°ã®ã³ã¼ã«ããã¯é¢æ°ãå¼ã°ãã¾ãã
ããã¯ãcomponentDidMount
ãå©ç¨ãã¦ãããããªã±ã¼ã¹ã«å©ç¨ã§ãã¾ãã
useEffect
ã¯ãé¢æ°ãæ»ãå¤ã¨ãã¦è¿ããã¨ãã§ãã¾ãã
æ»ãå¤ã¨ãã¦è¿ããé¢æ°ã¯Function Componentãã¢ã³ãã¦ã³ããããå ´åã«å¼ã³åºããã¾ãã
ããã¯ãµãã¹ã¯ãªãã·ã§ã³ã®ç»é²ã解é¤ãè¡ãããå ´åã«ä¾¿å©ã§ãã
ä¸è¨ã¯ãã¤ãã³ããã³ãã©ã¼ã®ç»é²ã解é¤ãuseEffect
ã使ã£ã¦è¡ãªãä¾ã§ãã
import React, {useEffect} from 'react'; const Resize = props => { useEffect(() => { const handler = () => { // ... }; window.addEventListener('resize', handler); // ã¤ãã³ãã解é¤ããé¢æ°ãè¿ã return () => window.removeEventListener(handler); }, []); return props.children; }
useEffect
ã¯Class Componentã§ã®componentDidMount
ã¨componentDidUpdate
ã¨ã¯éããDOMæ´æ°å¦çå¾ã«éåæã§å¼ã°ãã¾ãã
ãã®ãããcomponentDidMount
ã¨componentDidUpdate
ãDOMæ´æ°å¾ã«åæçã«å¼ã°ãããã¨ãä¿è¨¼ãããå ´åã«ã¯ãå¾è¿°ã®useLayoutEffect
ã使ç¨ãã¾ãã
useContext
useContext
ã¯æåéããContextãå©ç¨ããããã®Hookã§ãã
React.createContext
ã§ä½æããããªãã¸ã§ã¯ããå¼æ°ã¨ãã¦æ¸¡ãã¾ãã
Consumer
ã®Componentã渡ãããã§ã¯ãªãç¹ã«æ³¨æãã¦ãã ããã
import React, {useContext} from 'react'; const ThemeContext = React.createContext('normal'); const Button = props => { const theme = useContext(ThemeContext); return ( <button className={`${theme}-btn`} onClick={props.onClick}> {props.text} </button> ); }
useReducer
useReducer
ã¯ãreducer
ã¨initialState
ã渡ãã¨ãstate
ã¨dispatch
é¢æ°ãè¿ãHookã§ãã
Reduxãã¤ã¡ã¼ã¸ããã¨ããããããã¨æãã¾ãã
第ä¸å¼æ°ã¨ãã¦ãæåã«çºè¡ããActionããªãã¸ã§ã¯ãã¨ãã¦æ¸¡ããã¨ãå¯è½ã§ãã
import React, {useReducer} from 'react'; const reducer = (state, action) => { switch (action.type) { case 'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } }; const Counter = () => { const [state, dispatch] = useReducer(reducer, 0); return ( <div> <p>count is {state}</p> <button onClick={() => dispatch({type: 'INCREMENT'})}>++</button> <button onClick={() => dispatch({type: 'DECREMENT'})}>--</button> </div> ); };
useReducer
ã§ä½æã§ããã®ã¯Local Stateã§ãããContextã¨çµã¿åããããã¨ã§ãä¸è¨ã®ããã«Reduxã®ãããªã°ãã¼ãã«ãªStateãä½ããã¨ãå¯è½ã§ã¯ããã¾ãã
https://github.com/koba04/react-hooks-like-redux
ä½è«ã§ãããReduxã«é¢ãã¦ã¯ãã§ã«Issueã§è°è«ãããéããreact-redux
ã®connectã§è¡ãªã£ã¦ãããã¨ãHooksã®APIã¨ãã¦æä¾ãããã¨ãäºæ³ããã¾ãã
https://github.com/reduxjs/react-redux/issues/1063
ã¡ãªã¿ã«ãåè¿°ããuseState
ã¯useReducer
ã使ã£ã¦å®è£
ããã¦ãã¾ãã
useCallback
useCallback
ã¯ãå°ããããã«ãã§ãããã¡ã¢åãããé¢æ°ãè¿ãHookã§ãã
第äºå¼æ°ã«é
åã¨ãã¦æ¸¡ãããå¤ãå¤ãã£ãå ´åã®ã¿ã第ä¸å¼æ°ã§æ¸¡ããã³ã¼ã«ããã¯é¢æ°ãåçæããã¾ãã
ã¤ã¾ãã第äºå¼æ°ã«é
åã¨ãã¦æ¸¡ããå¤ãå¤ãããªãéããåãã³ã¼ã«ããã¯ãåå¾ã§ãã¾ãã
ããã¯PureComponentãReact.memoã使ã£ãå ´åãªã©ãåã®ComponentãPropsã§æ¸¡ãããå¤ãæ¯è¼ãã¦æé©åãè¡ãªã£ã¦ããå ´åã«æå¹ã§ãã
import React, {useCallback} from 'react'; // React.memoã使ã£ãæé©å const Child = React.memo(props => ( <div> <p>{props.name}({props.score})</p> <button onClick={props.onClick}>++</button> </div> )); const Parent = () => { const [score, setScore] = useState(0); // scoreãå¤ãã£ãå ´åã®ã¿åçæããã const onClick = useCallback(() => { setScore(score + 1); }, [score]); // ããæ¸ãã¨æ¯åæ°ããé¢æ°ã渡ããã¦ãã¾ã // const onClick = () => setScore(score + 1); return <Child onClick={onClick} name="child" score={score} />; };
useMemo
useMemo
ã¯ãuseCallback
ã¨å°ãä¼¼ã¦ãã¾ããããã¡ãã¯ã¡ã¢åãããã³ã¼ã«ããã¯ã§ã¯ãªãå¤ãè¿ãHookã§ãã
第äºå¼æ°ã«é
åã¨ãã¦æ¸¡ãããå¤ãå¤ãã£ãå ´åã®ã¿ã第ä¸å¼æ°ã§æ¸¡ããã³ã¼ã«ããã¯é¢æ°ãåè©ä¾¡ãã¦å¤ãè¿ãã¾ãã
ä¾ãã°ãPropsã¨ãã¦æ¸¡ããã巨大ãªãªã¹ãããã£ã«ã¿ãªã³ã°ãããããªãè¨ç®ã«æéã®ãããã±ã¼ã¹ã§ä½¿ããã¨ãæ³å®ããã¾ãã
import React, {useMemo} from 'react'; const ItemList = props => { // itemsãtypeãå¤ãã£ãå ´åã®ã¿åè©ä¾¡ããã const filteredItems = useMemo(() => ( props.items.filter(item => item.type === props.type) ), [props.items, props.type]) return ( <ul> {filteredItems.map(item => ( <Item key={item.id} item={item} /> ))} </ul> ); };
useRef
useRef
ã¯ããã®ååã®éãRef
ãæ ¼ç´ããããã®ãªãã¸ã§ã¯ããä½æãããã¨ã主ãªç¨éã®Hookã§ãã
ã¡ãªã¿ã«Ref
以å¤ã®ãªãã¸ã§ã¯ããæ ¼ç´ã§ãã¾ãã
import React, {useRef, useEffect} from 'react'; // ãã¦ã³ãæã«ã ããã©ã¼ã«ã¹ããã¦ã const Input = props => { const el = useRef(null); useEffect(() => { el.current.focus(); }, []) return <input type="text" ref={el} {...props} />; }
useImperativeMethods
useImperativeMethods
ã¯ãforwardRef
ã使ã£ã¦Ref
çµç±ã§è¦ªããã¢ã¯ã»ã¹ãããéã«ãRef
ã®ãªãã¸ã§ã¯ããã«ã¹ã¿ãã¤ãºããããã®Hookã§ãã
ã¦ã¼ã¹ã±ã¼ã¹ã¨ãã¦ã¯å¤ããªãã¨æãã¾ãã
import React, {useImperativeMethods, useRef} from 'react'; const MyInput = forwardRef((props, ref) => { const el = useRef(null); useImperativeMethods(ref, () => ({ focus: () => { el.current.focus(); } })); return <input type="text" ref={el} {...props} />; }); // ããã§åå¾ã§ããrefã¯focusã¡ã½ããã®ã¿æã£ããªãã¸ã§ã¯ã // <MyInput ref={ref} />
useMutationEffect
useMutationEffect
ã¯ãå©ç¨æ¹æ³ã¯useEffect
ã¨åãã§ãããå®è¡ãããã¿ã¤ãã³ã°ãç°ãªãHookã§ãã
useMutationEffect
ã¯ãReactãDOMãæ´æ°ããã®ã¨åãã¿ã¤ãã³ã°ã§åæçã«å¼ã³åºããã¾ãã
DOMãæ´æ°ãããã¿ã¤ãã³ã°ã§å¦çããããå ´åã«å©ç¨ãã¾ãã
DOMæ´æ°ä¸ã«åæçã«å®è¡ããããããDOMã®ããããã£ã«ã¢ã¯ã»ã¹ãããã¨ã§å¼·å¶çãªã¬ã¤ã¢ã¦ãã®åè¨ç®ãè¡ãããããã©ã¼ãã³ã¹ã«æªãå½±é¿ãä¸ããå¯è½æ§ãããã¾ãã åºæ¬çã«ã¯å©ç¨ãããã¨ãé¿ããã¹ãHookã ã¨æãã¾ãã
useLayoutEffect
useLayoutEffect
ã¯ãuseMutationEffect
ã¨ä¼¼ã¦ãã¾ããããã¡ãã¯å
¨ã¦ã®DOMã®æ´æ°å¦çãçµãã£ãã¿ã¤ãã³ã°ã§åæçã«å¼ã°ããHookã§ãã
æ´æ°å¾ã®ã¬ã¤ã¢ã¦ãæ
å ±ãDOMããåæçã«åå¾ãããå ´åã«å©ç¨ãã¾ãã
ããã¯ãClass Componentã®componentDidMount
ã¨componentDidUpdate
ãå¼ã°ããã¿ã¤ãã³ã°ã¨åãã¿ã¤ãã³ã°ã§å¼ã°ãã¾ãã
DOMã®æ´æ°å¦çã®å¾ã«åæçã«å¼ã°ãããããå¯è½ã§ããã°useEffect
ã使ãæ¹ãæã¾ããã§ãã
useEffect
ã¨useMutationEffect
ã¨useLayoutEffect
ã®ã¿ã¤ãã³ã°ãUser Timingã§ç¤ºãã¨ä¸è¨ã®éãã§ãã
useMutationEffect
ãHost Effectsï¼DOMã®æ´æ°å¦çã®ã¿ã¤ãã³ã°ï¼ã«ãuseLayoutEffect
ãã©ã¤ããµã¤ã¯ã«ã¡ã½ããå¼ã³åºãã®ã¿ã¤ãã³ã°ã«ãuseEffect
ããã®ãã¨éåæã«å¼ã°ãã¦ãããã¨ããããã¾ãã
Hooksã®å¶é
Hooksã¯Function Componentã®é¢æ°å ããå¼ã³åºãå¿ è¦ãããã¾ãã ã¾ããHooksã¯Function Componentå ã«ããã¦ãæ¯ååãé çªã§å¼ã³åºãå¿ è¦ãããã¾ãã ãã®ãããé¢æ°å ã®ãããã¬ãã«ã§å¼ã³åºããã¨ãæ¨å¥¨ããã¦ãã¾ããæ¡ä»¶åå²ãªã©ã®ä¸ã§å¼ã³åºããã¨ã¯é¿ããå¿ è¦ãããã¾ãã
const Foo = props => { // OK const [foo, setFoo] = useState('foo'); if (props.bar) { // NG const [bar, setBare] = useState('bar'); } return null; }
ããããã§ãã¯ããããã®eslint-plugin-react-hooks
ã¨ããESLintã®ãã©ã°ã¤ã³ãåæã«å
¬éããã¦ãã¾ãã
https://reactjs.org/docs/hooks-rules.html#eslint-plugin
ãã®å¶éã¯ãå°ããããã«ããæãã¾ãããHooksã®APIãã·ã³ãã«ã«ããããã®ãã¬ã¼ããªãã¨ãã¦é¸æããã¨ã®ãã¨ã§ãã
Custom Hooksã®ä½ãæ¹
Hooksã¯åè¿°ããéãFunction Componentããå¼ã³åºãå¿ è¦ãããã¾ãããç¬èªã«å®ç¾©ããHooksã®ä¸ã§å¼ã¶ãã¨ãå¯è½ã§ãã ãã®ãããç¹å®ã®å¦çãHooksã¨ãã¦å ±éåãããã¨ãå¯è½ã§ãã
Custom Hooksã¯åè¿°ããeslint-plugin-react-hooks
ã§ã®ãã§ãã¯ãæå¹ã«ããããã«ããuse
ããå§ã¾ãååã§ä½æãããã¨ãæ¨å¥¨ããã¦ãã¾ãã
ä¾ãã°ãwindowã®ãµã¤ãºãè¿ãHookã¯ãä¸è¨ã®ããã«ä½æãã¦ä½¿ç¨ã§ãã¾ãã
ä½æããHookã¯ãComponentã®æç»ã«ã¯ä¸åé¢ä¸ãã¦ããªããããã©ãã§ãåå©ç¨ã§ãã¾ãã
useWindowSize.js
import {useState, useEffect} from 'react'; // windowãµã¤ãºãè¿ãHook export const useWindowSize = () => { const [width, setWidth] = useState(window.innerWidth); const [height, setHeight] = useState(window.innerHeight); useEffect(() => { const handler = () => { setWidth(window.innerWidth); setHeight(window.innerHeight); } window.addEventListener('resize', handler); return () => window.addEventListener(handler); }, []); return [width, height]; }
- ä½æããHookã使ã
import React from 'react'; import {useWindowSize} from './useWindowSize'; const WindowSize = () => { const [width, height] = useWindowSize(); return <p>width:{width}, height:{height}</p> };
Hooksã®ç®ç
Componentã®ãã¸ãã¯ãåå©ç¨ããããã®æ段ã¨ãã¦ã¯ãHigher Order Component(以ä¸HOC)ãRender Propsã®ãã¿ã¼ã³ãããã¾ããã
ãããã¯ã³ã¼ããç解ããã®ãé£ããã£ããã"wrapper hell"ã¨å¼ã°ãã大éã®Componentã®ãã¹ããä½æãããã¨ããåé¡ãããã¾ãã
recompose
ã®ãããªHOCã®ã¦ã¼ãã£ãªãã£ã©ã¤ãã©ãªã使ã£ã¦ãã¦ãæ°ã¥ããªããã¡ã«"wrapper hell"ãä½ã£ã¦ãã¾ã£ã¦ããã±ã¼ã¹ãå¤ãã¨æãã¾ãã
"wrapper hell"ã¯ãããã°ã®é£ããããè¦ãç®ã®Viewã®æ§é 以ä¸ã«Componentã大éã«ãã¹ããããã¨ã§ãããã©ã¼ãã³ã¹ã«å¯¾ãã¦ãå½±é¿ãããã¾ãã
ã¾ãããµã¼ãã¼ããã®ãã¼ã¿åå¾ãã¤ãã³ãã®è³¼èªã»è§£é¤ã®å¦çãClass Componentãæã¤ã©ã¤ããµã¤ã¯ã«ã¡ã½ããã使ã£ã¦è¨è¿°ãããã¨ããã¨ãã³ã¼ããåé·ã«ãªã£ããã©ã¤ããµã¤ã¯ã«æ¯ã«å¦çãåæãã¦ãã¾ãåé¡ãããã¾ãã å ãã¦ãå ¨ã¦ã®ã©ã¤ããµã¤ã¯ã«ã¡ã½ããã®æåãç解ãã¦é©åã«ãã¸ãã¯ãæ¸ãã®ã¯é£ããã¨ããæè¦ãããã¾ãã
Hooksã¯ãComponentã®ãã¸ãã¯ã®åå©ç¨ãããããã®Primitiveã¨ãã¦æ°ããææ¡ããã¾ããã
ä¾ãã°Hooksã§ã¯ãComponentã®ã©ã¤ããµã¤ã¯ã«ã«ã¤ãã¦èããå¿
è¦ã¯ãªããuseEffect
ãªã©ã使ããã¨ã§ããã®å¤ãå¤ãã£ãããã®å¦çããããã¨ãã£ãããã«ãè¡ãããå¦çã ãã«æ³¨ç®ãã¦ãã¸ãã¯ãæ¸ããã¨ãã§ãã¾ãã
Reactã§ã¯ãFunction Componentã使ã£ãã¨ãã¦ãå é¨ã§ã¯Fiberã¨å¼ã°ãã¦ãããã¼ã¿æ§é ã§ç¶æ ãä¿æãã¦ãã¾ãã ã©ã¤ããµã¤ã¯ã«ã¡ã½ãããComponentã«å¯¾ãããã¤ã¬ãã«ãªAPIã§ãã£ãã®ã«æ¯ã¹ã¦ãHooksã¯Fiberã®å é¨æ§é ã«å¯¾ããPrimitiveãªAPIã¨ãã¦èãããã¨ãã§ãã¾ãã ããã¯ã@dan_abramovãKeynoteã®ä¸ã§ãAtom(åå)ã«å¯¾ããElectron(é»å)ã®ããã«ãHooksã¯Reactãæ§æããåæ©è½ã®è¦ç´ ã§ããã¨è¨ã£ã¦ãããã¨ããããããã¾ãã
Reactå é¨ã®ä»çµã¿ã«ã¤ãã¦ã¯ãbuilderscon 2018ã§è©±ããã®ã§ãèå³ããã°è¦ã¦ã¿ã¦ãã ããã
https://speakerdeck.com/koba04/algorithms-in-react
ä½è«ã§ãããHooksã®ã½ã¼ã¹ãè¦ã¦ã¿ãã¨ãHooksã¯Updateã¨åæ§ã«åæ¹åã®Linked Listã¨ãã¦å®è£ ããã¦ããã®ããããã¾ãã
ããããFunction Componentãå¦çããã度ã«åæåãã¦é çªã«ã¢ã¯ã»ã¹ãã¦ãããããå¿
ãåãé çªã«å¼ã³åºãå¿
è¦ãããã¾ãã
ä»ã«ããuseReducer
ã®å®è£
ãè¦ã¦ã¿ãã¨ãæ´æ°å¦çã¯æ¢åã®setStateã®ä»çµã¿ã¨ã¯éããå¥ã®æ´æ°ãã¥ã¼ãä½ã£ã¦å¦çãã¦ãããã¨ãããã£ããã¨èå³æ·±ãé¨åãå¤ãã§ãã
Keynoteã§ã¯ãHooksã®å°å
¥ã®çç±ã¨ãã¦JavaScriptã«ãããClassããæ©æ¢°ã«ã¨ã£ã¦ã人éã«ã¨ã£ã¦ãæ±ããé£ãããã®ã§ããç¹ãããã¦ãã¾ãã
ä¾ãã°ãã¤ãã³ããã³ãã©ã¼ç»é²æã®this
ã®æ±ãã«ããã人ãå¤ãã£ãããconstructorãªã©ã®å®åã³ã¼ãã«ããã³ã¼ãéãå¤ããªã£ããã
ã¾ããFunction Componentã§æ¸ããå¾ã«ç¶æ
ãã©ã¤ããµã¤ã¯ã«ã¡ã½ãããå¿
è¦ã«ãªã£ãéã«å¤§ããã³ã¼ããæ¸ãæããå¿
è¦ããããªã©ãClassãDeveloper Experienceã«ä¸ããå½±é¿ãææãã¦ãã¾ãã
ã¾ããæ©æ¢°ã«ã¨ã£ã¦ãClassã¯æé©åï¼ããããã£åãã¡ã½ããåã®minifyãå¦çã®ã¤ã³ã©ã¤ã³åãªã©ï¼ãé£ããã¨ããåé¡ç¹ãææãã¦ãã¾ãã ãã®è¾ºãã¯ãPrepackã§åãçµãã§ããçµæã¨ãã¦ã®çµè«ãªã®ããªã¨æãã¾ãã ä»ã«ãHot Reloadingãé£ããã¨ããåé¡ãããããã§ãã
ç¾ç¶ãgetSnapshotBeforeUpdate
ãcomponentDidCatch
ãgetDerivedStateFromError
ãªã©ãä¸é¨ã®ã©ã¤ããµã¤ã¯ã«ã¡ã½ããã«å¯¾å¿ããHooksãæä¾ããã¦ãã¾ãããããããããããæä¾ãããäºå®ã¨ã®ãã¨ã§ãã
å°æ¥çã«ã¯Hooksã使ããã¨ã§ãClass Componentãå»æ¢ããæµãã«ãªããã¨æãã¾ãã ã§ãããããã¯è¿ãå°æ¥ã®è©±ã§ã¯ãªãã®ã§ãæ ã¦ãå°ããã¤è©¦ãã¦ããã®ãããããªã¨æãã¾ãã
Concurrent React
Hooksã話é¡ã«ãªãä¸ã次ã®æ¥ã®Keynoteã§ã¯Concurrent Reactã«ã¤ãã¦çºè¡¨ããã¾ããã
ãã¼ã¯ã®åç»ã¯ä¸è¨ã§ãã ãã¢ä¸»ä½ã§ããããããã®ã§ãæ¯éãã¼ã¯ã観ããã¨ããªã¹ã¹ã¡ãã¾ãã
- Concurrent Rendering in React - Andrew Clark and Brian Vaughn
ã¾ãããã¡ãã®ãã¼ã¯ã§ã¯å®éã«Suspenseã使ã£ããã¢ãè¡ããã¾ããã
- Moving To Suspense - Jared Palmer
Concurrent Reactã¯ãããã¾ã§Async Renderingã¨å¼ã°ãã¦ãããã®ã§ãSuspenseã¨Time-slicingã®2ã¤ã®æ©è½ãæãã¾ãã ä»åã¯æ°ããä½ããçºè¡¨ãããã¨ãããããç¾å¨ã®ç¶æ³ãæ¹ãã¦èª¬æãã¦ããã¢ã§ã©ããã£ããã¨ãå¯è½ã«ãªããã示ããã®ã§ããã
Suspense
Suspenseã¯ã¬ã³ããªã³ã°ãä¸æï¼Suspendï¼ã§ããæ©è½ã§ãã Suspenseã¯ãPromiseï¼æ£ç¢ºã«ã¯thenableãªãªãã¸ã§ã¯ãï¼ãPrimitiveã¨ãã¦æ±ããããAPIããã®ãã¼ã¿åå¾ãComponentã®åçãªèªã¿è¾¼ã¿ã ãã§ãªããPromiseã§ã©ãããããã¨ã§æ§ã ãªéåæå¦çã«å¯¾ãã¦é©ç¨ã§ãã¾ãã
Suspenseã®ä»çµã¿ã«ã¤ãã¦ã¯ãéå»ã«ä½åº¦ãç´¹ä»ãã¦ããã®ã§èå³ããã°åç §ãã¦ãã ããã ã¹ã©ã¤ãã§ç´¹ä»ãã¦ãããã¼ã¸ã§ã³ããAPIã®ååã¯å¤ãã£ã¦ãã¾ãããåºæ¬çãªä»çµã¿ãã³ã³ã»ããã¯åãã§ãã
æ¬ãã¼ã¯ã§ã¯ãReact.lazyã¨çµã¿åãããåçãªComponentèªã¿è¾¼ã¿ãç´¹ä»ããã¦ãã¾ããã
import React from 'react'; const LazyContent = React.lazy(() => import('./LazyContent')); const App = () => ( <main> <section> <p>Main</p> <React.Suspense fallback={"loading..."}> <LazyContent /> </React.Suspense> </section> </main> );
ä¸è¨ã§ã¯ãLazyContent
ã®Componentãåçã«èªã¿è¾¼ãã§ãèªã¿è¾¼ã¾ããã¾ã§ã¯loading...
ã®ã¡ãã»ã¼ã¸ã表示ãã¦ãã¾ãã
ä¸è¨ã®å ´åãloading...
ã®ã¡ãã»ã¼ã¸ãå³åº§ã«è¡¨ç¤ºããã¾ãããå¾è¿°ããReact.ConcurrentMode
ã使ããã¨ã§æå®ç§çµéå¾ã«ãã¼ãã£ã³ã°ãã¡ãã»ã¼ã¸ãåºãã¨ãã£ããã¨ãå¯è½ã«ãªãã¾ãã
ä¸è¨ã§ä½¿ç¨ãã¦ããåçãªimportã¯ã¾ã ææ¡æ®µéã®ä»æ§ã§ãããwebpackãªã©ã使ã£ã¦ããå ´åã«ã¯ãã§ã«å©ç¨å¯è½ã§ãã
ãã®ããã«React.lazy
ã使ããã¨ã§ãwebpackãªã©ã®ãã¼ã«ã®ãµãã¼ãã¯å¿
è¦ã§ãããimportã®ä»æ¹ãå¤ããã ãã§ç°¡åã«åçãªèªã¿è¾¼ã¿ãå¯è½ã¨ãªãã¾ãã
React.Suspense
ã®Componentã¯è¦ªã®ä½ç½®ã§ããã°ã©ãã§ãé
ç½®å¯è½ãªããããã¼ãã£ã³ã°ã§é ãç¯å²ãç°¡åã«æå®ã§ãã¾ãã
ã¾ããä¾ãã°è¤æ°ã®éåæãªä¾åé¢ä¿ãããå ´åã«ããããã®è¦ªã«React.Suspense
ãå®ç¾©ãããã¨ã§ãå
¨ã¦ã®éåæã®ä¾åã解決ããã¾ã§ãåä¸ã®ãã¼ãã£ã³ã°ã¡ãã»ã¼ã¸ãåºãã¨ãã£ããã¨ãå¯è½ã§ãã
ãã®ããã«Suspenseã使ããã¨ã§ãæè»ãªéåæèªã¿è¾¼ã¿ã®å¶å¾¡ãå¯è½ã§ãã
APIãªã¯ã¨ã¹ãã«ã¤ãã¦ã¯ãreact-cache
ã¨ããããã±ã¼ã¸ã®unstable_createResource
ãå©ç¨ãããã¨ãä¸è¨ã®éãè¨è¿°ã§ãã¾ãã
ãã ããreact-cache
ã«ã¤ãã¦ã¯ã¾ã Stableã§ã¯ãªãããã£ãã·ã¥ã®Invalidationãªã©æ¬ ãã¦ããæ©è½ãããã®ã§ã¾ã å®éã«å©ç¨ã§ããã¬ãã«ã§ã¯ãªãã¨ãã¦ãã¾ãã
import React from 'react'; import {unstable_createResource as createResource } from 'react-cache'; // Promiseãè¿ãå¦çãããªã½ã¼ã¹ãä½æãã const userResource = createResource(id => { return fetch(`/api/user/${id}`).then(res => res.json()) }); const User = props => { // ãªã½ã¼ã¹ãèªã¿è¾¼ã // ãªã½ã¼ã¹ããã£ãã·ã¥ããã¦ããªãå ´åã¯ãPromiseãthrowãããã®ã§ã¬ã³ããªã³ã°ãæ¢ã¾ã const user = userResource.read(props.id); return <div>{user.name}</div>; } const App = () => ( <React.Suspense fallback="loading..."> <User id={1} /> </React.Suspense> );
ä¸è¨ã§ã¯ãæå®ããã¦ã¼ã¶ã®ãã¼ã¿ããã£ãã·ã¥ã«ãªããã°fetch
ã使ã£ã¦APIããåå¾ãã¦ãã£ãã·ã¥ã«æ ¼ç´ãã¾ãã
ãã®éãReact.Suspense
ã«æå®ãã¦ããloading...
ã®ã¡ãã»ã¼ã¸ã表示ããã¾ãã
ãã®å¾ãAPIã¬ã¹ãã³ã¹ãåãåãã¨ã¬ã³ããªã³ã°ãåéããã¾ãã
åéæã«ã¯ãAPIã¬ã¹ãã³ã¹ã®ãã¼ã¿ããã£ãã·ã¥ã«æ ¼ç´ããã¦ãã¦ãã¼ã¿ãåæçã«åå¾ã§ããã®ã§ãUser
ã表示ããã¾ãã
Suspenseã®å¿ç¨ä¾ã¨ãã¦ã"Moving To Suspense"ã®ãã¼ã¯ã§ã¯ãReact.Suspense
ã®fallback
ã«ä½è§£å度ã®ç»åãæå®ãã¦ãé«è§£å度ã®ç»åãéåæã«ãã¼ããããã¨ã§ãæåã¯ä½è§£å度ã®ç»åã表示ãã¦ãã®ãã¨é«è§£å度ã®ç»åã«å·®ãæ¿ããã¨ãã£ããã¢ãè¡ããã¦ã¾ããã
ã¾ããunstable_createResource
ã§ä½æãããªã½ã¼ã¹ã¯read
ã ãã§ãªãpreload
ã¨ããã¡ã½ãããæã£ã¦ãã¦ãããã使ããã¨ã§ãäºåã«ãã¼ã¿ããã£ãã·ã¥ãã¦ãããã¨ãå¯è½ã§ãã
Suspenseã«ã¤ãã¦ã¯ãä¸è¨ã®ãããªåæã¢ã¼ãã§ã®åºæ¬çãªæåã«ã¤ãã¦ã¯ããã§ã«Stableã ã¨ãã¦ãã¾ãããreact-cache
ã使ã£ãAPIãã¼ã¿ã®åå¾ãReact.ConcurrentMode
ã使ã£ãConcurrentModeã«ã¤ãã¦ã¯ãã¾ã ã¾ã Stableã§ãªãã¨ãã¦ãã¾ãã
ConcurrentModeã¯ã¬ã³ããªã³ã°ãéåæã«ããããã®ã¢ã¼ãã§ãã
React.ConcurrentMode
ã®Componentã§å²ããã¨ã§ããã®ä¸ã¯ConcurrentModeã«ãªãã¾ãã
ã¾ããReactDOM.createRoot(domElement).render(ReactElement)
ã¨ããæ°ããæ¹æ³ã§DOMããã¦ã³ããããã¨ã§ãå
¨ä½ãConcurrentModeã«ãããã¨ãå¯è½ã§ãã
é¨åçã«éåæã¬ã³ããªã³ã°ãå°å
¥ãããå ´åã«ã¯React.ConcurrentMode
ãã¢ããªã±ã¼ã·ã§ã³å
¨ä½ãéåæã¬ã³ããªã³ã°ãããå ´åã«ã¯ReactDOM.createRoot
ã使ãã¾ãã
ConcurrentModeã§ã¯ãReact.Suspense
ã«maxDuration
ãæå®ã§ãã¾ãã
maxDuration
ãæå®ãããã¨ã§ãfallback
ã表示ããã¾ã§ã®æéãå¶å¾¡ã§ãã¾ãã
ä¾ãã°ããããã¯ã¼ã¯ç°å¢ãããã¦APIãªã¯ã¨ã¹ãã1ç§ä»¥å
ã«è¿ã£ã¦ãããããªç¶æ³ã§ã¯ããã¼ãã£ã³ã°ã表示ããã«ãã¼ã¿ã®ãã¼ããå¾
ã£ã¦è¡¨ç¤ºããæ¹ãã¹ã ã¼ãºã§ãã
ãããã£ãå ´åã«ãmaxDuration={1000}
ã®ããã«æå®ãããã¨ã§ã1ç§çµéãã¦ãããã¼ãã£ã³ã°ã表示ããã¨ãã£ãå¶å¾¡ãå¯è½ã§ãã
Suspenseãå©ç¨ãããã¨ã§ãéåæãªä¾åé¢ä¿ã®å¶å¾¡ãç°¡åã«æè»ã«ã§ããããã«ãªãã¾ãã
ã¾ãããµã¼ãã¼ãµã¤ãã¬ã³ããªã³ã°å¯¾å¿ã«ã¤ãã¦ãåãçµãã§ããããã§ãã
This week, @sebmarkbage is starting work on the Suspense-powered streaming server renderer that we've been teasing. So excited for this project. (Earlier this year I demoed a rough prototype: https://t.co/YFjbyU8uYs)
— Andrew Clark (@acdlite) October 30, 2018
Time-slicing
Time-slicingã¯ãæ´æ°å¦çãåªå
度ä»ãåºæ¥ãæ©è½ã§ãã
ãã©ã¤ãªãªãã£ããã¼ã¹ã¨ããå調çãã«ãã¿ã¹ã¯ã«ãããã¡ã¤ã³ã¹ã¬ããããããã¯ããªãæ´æ°å¦çãå¯è½ã«ãã¾ãã
ããã«ã¯scheduler
ã¨ããããã±ã¼ã¸ãå©ç¨ãã¾ãã
Reactã§ã¯ãclick
ãinput
ãtouchmove
ãªã©ãã¦ã¼ã¶ã¼ãããã«ãªã¢ã¯ã·ã§ã³ãæå¾
ãããããªã¤ãã³ãã«å¯¾ãã¦ã¯ãInteractiveUpdateã¨ãã¦é«ãåªå
度ãå²ãå½ã¦ããã¾ãã
ãã ãã巨大ãªã¹ãã¬ããã·ã¼ãã«å¯¾ãããã£ã«ã¿ãªã³ã°ãªã©ãä¸åº¦ã®æ´æ°å¦çãéãå ´åã«ã¯ã¦ã¼ã¶ã¼å
¥åãä»ã®æ´æ°ããããã¯ãã¦ãã¾ãã¾ãã
ãã®å ´åãã¦ã¼ã¶ã¼ã«ã¨ã£ã¦ã¯ãã£ã«ã¿ãªã³ã°ããããã®ããã¹ãããã¯ã¹ã¯ããã«åæ ããã¦æ¬²ããã¦å
¥åããããã¯ãã¦æ¬²ãããªãã§ããããã£ã«ã¿ãªã³ã°ããçµæã®è¡¨ç¤ºã«ã¤ãã¦ã¯ãå°ããããé
ãã¦ãåé¡ãªããã¨ãå¤ãã§ãã
ãã®ãããªå ´åã«ãTime-slicingã使ããã¨ã§ããã£ã«ã¿ãªã³ã°ããããã®ããã¹ãããã¯ã¹ã¸ã®æ´æ°å¦çã¯åªå 度é«ãåæ ãã¦ããã£ã«ã¿ãªã³ã°ããçµæã«ã¤ãã¦ã¯åªå 度ãä¸ãã¦åæ ãé ããããã¨ãå¯è½ã¨ãªãã¾ãã
import React, {useState, useMemo} from 'react'; // ãã®ãã¼ã¸ã§ã³ã¯ã¾ã npmã«publishããã¦ããªã import {scheduleCallback} from 'scheduler'; const App = props => { const [text, setText] = useState(''); const [filterText, setFilterText] = useState(''); const filteredItems = useMemo(() => ( props.items.filter(item => item.indexOf(filterText) !== -1) ), [filterText, props.items]); return ( <main> <Input value={text} onChange={value => { setText(value); // Filterããæ¹ã®åªå 度ã¯ä¸ãã scheduleCallback(() => { setFilterText(value); }) }} /> <List items={filteredItems} /> </main> ); };
ä¸è¨ã®ãµã³ãã«ã¯ã使ç¨ãã¦ããscheduler
ã®scheduleCallback
ãã¾ã å
¬éããã¦ããªãããåä½ãã¾ãããã以åä½æãããã¢ãããã®ã§ããã¡ãã試ãã¦ãããã¨é°å²æ°ãæ´ããããªã¨æãã¾ãã
- https://react-timeslicing-demo.netlify.com/
- https://github.com/koba04/react-timeslicing-demo (Repository)
ã¾ããConcurrentModeã§ã¯ãhidden
ã®Propsã«ããã¡ã¤ã³ã¹ã¬ãããéªéããªãããªã¬ã³ããªã³ã°ãå¯è½ã§ãã
hidden
ã®PropsãDOM Componentã«æå®ãããã¨ã§ããã®åè¦ç´ ã¯OffScreen Priorityã¨ããç¹æ®ãªåªå
度ã§å¦çããã¾ãã
OffScreen Priorityã¯ã¨ã¦ãä½ãåªå
度ã¨ãã¦å®ç¾©ããã¦ãããããä»ã®æ´æ°å¦çããããã¯ãã¾ããã
ãã¼ã¯ã§ã¯ã¿ãUIã®ä¾ã示ããã¦ãã¾ããããã¦ã¼ã¶ã¼ã表示ãããã¼ã¸ãå ã«èªã¿è¾¼ãã§ãããã¨ã§é«éãªãã¼ã¸é·ç§»ãå®ç¾ã§ãã¾ãã Suspenseã¨çµã¿åããããã¨ã§ãäºåã«éåæã®ä¾åé¢ä¿ããã¼ããããã¨ãå¯è½ã«ãªããããããã«å¼·åãªä»çµã¿ã¨ãªãã¾ãã
const Home = React.lazy(() => import('./Home')); const User = React.lazy(() => import('./User')); const Settings = React.lazy(() => import('./Settings')); const App = props => ( <main> <div hidden={props.page === "user"}> <User /> </div> <div hidden={props.page === "settings"}> <Settings /> </div> <div hidden={props.page !== "user" && props.page !== "settings"}> <Home /> </div> </main> );
ä¸è¨ã§ã¯ãæåã«è¡¨ç¤ºãããã¼ã¸ä»¥å¤ãåçã«èªã¿è¾¼ãã§ããªã¬ã³ããªã³ã°ãã¦ãã¾ãã
ã¾ã¨ã
ãã®ããã«ä»åçºè¡¨ãããå 容ã¯ãReactã使ã£ãã¢ããªã±ã¼ã·ã§ã³ã®ä½ãæ¹ãå¤ãã大ããªãã®ã§ããã ããããReduxãªã©ã®å¨è¾ºã©ã¤ãã©ãªããããã®APIãã©ã使ããã«ãããã¾ãããæéãããã¦ä»åç´¹ä»ãããå 容ã使ã£ãæ¸ãæ¹ã«å¤ãã£ã¦ããã¨æãã¾ãã ã¾ã å®å®çã¨ãã¦ãªãªã¼ã¹ãããã¨ããç¶æ³ã§ã¯ãªãã®ã§ãçãä¸ããã«è¸ããããå°ããã¤è©¦ãã¦ããã®ãããããã§ãã
ã¾ããKeynoteã§ã¯ããã®ä»ã«ãæ°ããProfilerã«ã¤ãã¦ã®ç´¹ä»ããã£ãã®ã§ãã¡ãã注ç®ã§ãã
ãµã¤ãã¦ãºã®ããã³ãã¨ã³ãã¨ãã¹ãã¼ããã¼ã ã§ã¯ãä¸ç·ã«ããã³ãã¨ã³ãåéã®åé¡è§£æ±ºã«åãçµãã§ããã仲éãåéãã¦ãã¾ãã