æ¬èšäºã¯ é»éåœéæ
å ±ãµãŒãã¹ Advent Calendar 2021 ã® 13 æ¥ç®ã®èšäºã§ãã å·çè
㯠2021 幎æ°åå
¥ç€Ÿã® XI æ¬éš AI ãã©ã³ã¹ãã©ãŒã¡ã³ã·ã§ã³ã»ã³ã¿ãŒæå±ã®å±±ç°ã§ãã ã¯ããã« React Hooks ãšã¯ useState useState ã䜿ããŠãŒã¹ã±ãŒã¹ useEffect useEffect ã䜿ããŠãŒã¹ã±ãŒã¹ useContext useContext ã䜿ããŠãŒã¹ã±ãŒã¹ useReducer useReducer ã䜿ããŠãŒã¹ã±ãŒã¹ useMemo useMemo ã䜿ããŠãŒã¹ã±ãŒã¹ useCallback React.memo ãš useCallback useCallback ã䜿ããŠãŒã¹ã±ãŒã¹ React Hooks ãæ£ãã䜿ãããã« ãããã« ã¯ããã« æ¬èšäºã§ã¯ React Hooks ã®ä»£è¡šçãªããã¯ã«ã€ããŠããã®äœ¿ãæ¹ãš ãŠãŒã¹ã±ãŒã¹ ããµã³ãã«ã³ãŒããšãšãã«ç޹ä»ããŸãã ãµã³ãã«ã³ãŒã㯠TypeScript ã§èšè¿°ããŠããŸãã React Hooks ãšã¯ React Hooks 㯠React 16.8ïŒ2019 幎 2 æãªãªãŒã¹ïŒã§è¿œå ãããæ©èœã§ãã 2021 幎çŸåšã«ãã㊠React ã§ã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããããã«ã¯çè§£ãå¿
é ã®æ©èœãšãã£ãŠãéèšã§ã¯ãªãã§ãããã React Hooks ã䜿ãããšã«ãã£ãŠ React ã®é¢æ°å ã³ã³ããŒãã³ã ã§ç¶æ
ïŒstateïŒãæã€ããšã ã³ã³ããŒãã³ã ã®ã©ã€ããµã€ã¯ã«ã«å¿ããåŠçãèšè¿°ã§ããŸãã 以äžã§ã¯ã React ã§æäŸãããåºæ¬ç㪠React Hooks ããã® ãŠãŒã¹ã±ãŒã¹ ãšãšãã«ç޹ä»ããŸãã useState useEffect useContext useReducer useMemo useCallback useState useState ã¯é¢æ°å ã³ã³ããŒãã³ã ã§ç¶æ
ïŒstateïŒãæ±ãããã®ããã¯ã§ãã 以äžã¯ useState ãå©çšããå Žåã®åºæ¬çãªã³ãŒãã§ãã // è¿ãå€ã¯stateã®å€æ°ãšstateãæŽæ°ããããã®é¢æ° const [ state , setState ] = useState < T >( initStateValue ); useState ã¯ç¶æ
ïŒstateïŒã®å€æ°ãšç¶æ
ïŒstateïŒãæŽæ°ããããã®é¢æ°ãè¿ããŸãã ç¶æ
ïŒstateïŒã®æŽæ°ãããéã¯å¿
ãæŽæ°çšã®é¢æ°ãä»ããŠè¡ãå¿
èŠããããŸãã useState ã䜿ã ãŠãŒã¹ã±ãŒã¹ useState ãå¿
èŠãšãªãã®ã¯ãå©çšè
ãš ã€ã³ã¿ã©ã¯ãã£ã ã«ããåããããå€ãä¿æããå¿
èŠãããå Žåã§ãã å©çšè
ãš ã€ã³ã¿ã©ã¯ãã£ã ã«ããåãããããšããå Žé¢ã®æãå
žåçãªäŸã¯ãã©ãŒã ã§ãã ããã§ã¯ãã°ã€ã³ãã©ãŒã ã顿ã«ããŠã³ãŒãã玹ä»ããŸãã äœæãããã°ã€ã³ãã©ãŒã ã¯ç»åã®ããã« input èŠçŽ ãšããŠãŠãŒã¶ãŒ ID ãšãã¹ã¯ãŒããæã€ãã®ãæ³å®ããŸãã åãã«ãã°ã€ã³ãã©ãŒã ã§æ±ãããŒã¿ã®åïŒSampleLoginFormïŒãå®çŸ©ããŠãããŸãã ä»åã®äŸã§ã¯ userId ãš password ã ããããããã£ã«æã€ãªããžã§ã¯ããšããŸãã interface SampleLoginForm { userId: string ; password: string ; } äœã£ã SampleLoginForm åã®å€æ° formData ã useState ã䜿ã£ãŠå®çŸ©ããŸãã const [ formData , setFormData ] = useState < SampleLoginForm >( { userId: "" , password: "" , } ); ããšã¯ input èŠçŽ ã® value 屿§ã«å¯Ÿå¿ãã formData ã®å€æ°ãæž¡ããŸãã ããã« input èŠçŽ ã® onChange ã€ãã³ããã setFormData ãåŒã³åºã㊠formData ã®ç¶æ
ãæŽæ°ããŸãã < div > < label htmlFor = "userId" > ãŠãŒã¶ãŒID < /label > < input id = "userId" type= "text" name = "userId" placeholder = "ãŠãŒã¶ãŒID" value = { formData.userId } onChange = { ( e ) => setFormData ( { ...formData , userId: e.target.value } ) } / > < /div > ããã«ããããŠãŒã¶ãŒããã©ãŒã ã«å
¥åããæåïŒå€ïŒã倿° formData ã«ä¿æã§ããŸãã â¶ïž ã¯ãªãã¯ããŠã³ãŒãå
šæãèŠã // components/LoginForm.tsx import React , { useState } from "react" ; interface SampleLoginForm { userId: string ; password: string ; } export default function LoginForm () : JSX. Element { const [ formData , setFormData ] = useState < SampleLoginForm >( { userId: "" , password: "" , } ); const submitHandler = ( e: FormEvent < HTMLFormElement >) => { e.preventDefault (); console .log ( "ãã°ã€ã³ãã¿ã³æŒäž" , formData ); } ; return ( < form onSubmit = { submitHandler } > < div > < label htmlFor = "userId" > ãŠãŒã¶ãŒID < /label > < input id = "userId" type= "text" name = "userId" placeholder = "ãŠãŒã¶ãŒID" value = { formData.userId } onChange = { ( e ) => setFormData ( { ...formData , userId: e.target.value } ) } / > < /div > < div > < label htmlFor = "password" > ãã¹ã¯ãŒã < /label > < input id = "password" type= "password" name = "password" placeholder = "ãã¹ã¯ãŒã" value = { formData.password } onChange = { ( e ) => setFormData ( { ...formData , password: e.target.value } ) } / > < /div > < div > < button type= "submit" > ãã°ã€ã³ < /button > < /div > < /form > ); } useEffect useEffect ã¯é¢æ°å ã³ã³ããŒãã³ã ã§å¯äœçšãå®è¡ããããã®ããã¯ã§ãã å¯äœçšãšèããšä»°ã
ããã§ãã ã³ã³ããŒãã³ã å
ã§ã®ãå€éšããŒã¿ã®ååŸããDOM ã®æåã§ã®æŽæ°ããªã©ã®åŠçããReact ã§ã¯å¯äœçšãšåŒã³ãŸãã useEffect ã䜿ãããã®åºæ¬çãªã³ãŒãã¯ä»¥äžã®ãšããã§ãã // å¯äœçšãå«ãåŠçãèšè¿°ãã颿°ãèšè¿°ãã useEffect (() => { // å¯äœçšåŠç // ⊠return () => { // ã¯ãªãŒã³ã¢ããåŠç } ; } , [] ); useEffect ã§ã¯å¯äœçšãšãªãåŠçã颿°å
ã§èšè¿°ããŸãã return ã§é¢æ°ãè¿ãããšã«ãã£ãŠã¯ãªãŒã³ã¢ããåŠçãèšè¿°ã§ããŸãã éåžžã useEffect ã«ããå¯äœçšåŠç㯠ã³ã³ããŒãã³ã ã® ã¬ã³ããªã³ã° æ¯ã«å®è¡ãããŸãã å¯äœçšåŠçãæ¯åè¡ããªãããã«ã¯ã第2åŒæ°ã®äŸåé
åã«ãã£ãŠå¶åŸ¡ã§ããŸãã useEffect ã®è©³ãã説æã«ã€ããŠã¯ä»¥äžã®åèãªã³ã¯ãã芧ãã ããã å¯äœçšããã¯ã®å©çšæ³ https://ja.reactjs.org/docs/hooks-effect.html useEffectå®å
šã¬ã€ã https://overreacted.io/ja/a-complete-guide-to-useeffect/ useEffect ã䜿ã ãŠãŒã¹ã±ãŒã¹ useEffect ãå¿
èŠãšãªã代衚ç㪠ãŠãŒã¹ã±ãŒã¹ ãšããŠã¯ ã³ã³ããŒãã³ã ãåŒã³åºããã¿ã€ãã³ã°ã§å€éš API ãããªãœãŒã¹ãååŸãããå Žåãªã©ã§ãã ããã§ã¯ãµã³ãã«ã®å€éš API ãšã㊠JSONPlaceholder ãå©çšã㊠ã³ã³ããŒãã³ã ãåŒã³åºããã¿ã€ãã³ã°ã§ããŒã¿ãååŸããŠã¿ãŸãããã JSONPlaceholder, https://jsonplaceholder.typicode.com/ å°ã JSONPlaceholder ã«ã€ããŠè£è¶³ããŸãã JSONPlaceholder 㯠6 çš®é¡ã®æ§é ã®ãããŒããŒã¿ãååŸã§ããŸãã ä»åã¯ã¿ã¹ã¯ç®¡çã¢ããªã±ãŒã·ã§ã³ã§äžè¬ç㪠ToDo ãªã¹ã圢åŒã®ããŒã¿ãååŸããŸãã ååŸããããŒã¿ã¯å
ã»ã©ç޹ä»ãã setState ã䜿ã£ãŠä¿æããŸãã ãŸãååŸãã ToDo ãªã¹ãã®åãå®çŸ©ããŠãããŸãã interface ToDo { id: number ; userId: number ; title: string ; completed: boolean ; } ãããŠå
ã»ã©ã® useState ããã¯ã䜿ã£ãŠååŸãã ToDo ãªã¹ã圢åŒãç¶æ
管çããŸãã const [ todoItemss , setToDos ] = useState < ToDo [] >( [] ); ãã㊠useEffect ã䜿ã£ãŠå®éã«å€éš API ãåŒã³åºããç¶æ
ãæŽæ°ããŸãã å€éš API ã®åŒã³åºãã«ã¯ fetch ãå©çšããŸãã useEffect (() => { const f = async () => { const res: Response = await fetch ( "https://jsonplaceholder.typicode.com/todos" ); const json: ToDo [] = await res.json (); setToDos ( json ); } ; f (); } , [] ); 泚æç¹ãšã㊠useEffect ã«æž¡ã颿°ã¯åæçã§ãã ãã®ããéåæé¢æ°ïŒasync/awaitïŒã䜿ãã«ã¯é¢æ°å
ã§å®çŸ©ããå¿
èŠããããŸãã è£è¶³ã§ãããæ¬¡æã¢ããããŒãã® React v18 ãã React.Suspense ã䜿ã£ãéåæã®ããŒã¿ååŸããµããŒããããŸãã ã¢ããããŒãåŸã¯ãã¡ãããã¹ãã ã©ã¯ ãã£ã¹ã«ãªã£ãŠããå¯èœæ§ãé«ããããå
¬åŒããã¥ã¡ã³ãã®ãReact.Suspenseããšããµã¹ãã³ã¹ã䜿ã£ãããŒã¿ååŸãã«ã€ããŠãããã²ãã§ãã¯ãããŠã¿ãŠãã ããã React ã®æäžäœ API - React.Suspense, https://ja.reactjs.org/docs/react-api.html#reactsuspense ãµã¹ãã³ã¹ã䜿ã£ãããŒã¿ååŸïŒå®éšçæ©èœïŒ, https://ja.reactjs.org/docs/concurrent-mode-suspense.html ååŸãã ToDo ãªã¹ã圢åŒã®ããŒã¿ã¯ã¹ã¿ã€ã«ãå°ãåœãŠãŠ Array.prototype.map() ã䜿ãã°ä»¥äžã®ããã«æç»ã§ããŸãã â¶ïž ã¯ãªãã¯ããŠã³ãŒãå
šæãèŠã // components/ToDoList.tsx import React , { useEffect , useState } from "react" ; interface ToDo { id: number ; userId: number ; title: string ; completed: boolean ; } export default function ToDoList () : JSX. Element { const [ todoItems , setToDos ] = useState < ToDo [] >( [] ); useEffect (() => { const f = async () => { const res: Response = await fetch ( "https://jsonplaceholder.typicode.com/todos" ); const json: ToDo [] = await res.json (); setToDos ( json ); } ; f (); } , [] ); return ( < div style = {{ textAlign: "left" }} > { todoItems.map (( todoItem ) => ( < div key = { todoItem.id } style = {{ width: "250px" , border: "solid" , margin: "8px" , padding: "8px" , }} > < h4 > { todoItem.title } < /h4 > < p style = {{ textAlign: "right" }} > { todoItem.completed ? "â
å®äº" : "æªå®æœ" } < /p > < /div > )) } < /div > ); } useContext useContext 㯠ã³ã³ããŒãã³ã éã§æšªæçã«å©çšãããç¶æ
ã管çããããã®ããã¯ã§ãã éåžžã ã³ã³ããŒãã³ã ã§ã¯ç¶æ
ïŒããŒã¿ïŒã props ãéããŠèŠªããåã«æž¡ããŸãã ãããå³ã«èµ·ãããšä»¥äžã®ããã«ãªããŸãã äžæ¹ãã³ã³ããã¹ãã䜿ããšä»¥äžã®ããã« props ãéããã«ããŒã¿ãããåãã§ããŸãã ã³ã³ããã¹ãã§ã¯ Context.Provider ã³ã³ããŒãã³ã ãéããŠæšªæçã«å©çšãããç¶æ
ãé
ä¿¡ããŸãã ãããŠå¿
èŠãª ã³ã³ããŒãã³ã ã§ useContext ã䜿ãããšã«ãã£ãŠç¶æ
ã賌èªããŸãã useContext ã䜿ã£ãŠ ã³ã³ããŒãã³ã å
ã§ã³ã³ããã¹ãããé
ä¿¡ãããå€ã賌èªããåºæ¬çãªã³ãŒãã¯ä»¥äžã®ããã«ãªããŸãã // è¿ãå€ã¯ã³ã³ããã¹ãããé
ä¿¡ãããå€ // useContextã®ç¬¬1åŒæ°ã«ã¯`React.createContext`ã«ãã£ãŠäœæããã³ã³ããã¹ããªããžã§ã¯ããæž¡ã const value = useContext ( MyContext ); useContext ã§ã¯è³Œèªããã³ã³ããã¹ãã®ãªããžã§ã¯ããæž¡ããã³ã³ããã¹ãããé
ä¿¡ãããå€ãåãåããŸãã useContext ã䜿ã ãŠãŒã¹ã±ãŒã¹ ãããŸã§ã§è¿°ã¹ãŠããããã« useContext ã䜿ãã®ã¯ ã³ã³ããŒãã³ã éã§æšªæçã«å©çšãããç¶æ
ãããå Žé¢ã§ãã 代衚çãªå Žé¢ãšããŠèªèšŒæ
å ±ã®ç®¡çãªã©ããããŸãã ããã§ã¯ã³ã³ããã¹ãã䜿ã£ãŠãŠãŒã¶ãŒ ID ã管çããããšãäŸã«èª¬æããŸãã 管çãããŠãŒã¶ãŒ ID 㯠useState ãçšããŠå®£èšãããã®ç¶æ
ãšæŽæ°çšã®é¢æ°ãã³ã³ããã¹ãã䜿ã£ãŠé
ä¿¡ããŸãã // ã³ã³ããã¹ãã§é
ä¿¡ããå€ const [ userId , setUserId ] = useState < number >( -1 ); é
ä¿¡ããå€ã決ãŸã£ãã®ã§ãã³ã³ããã¹ãã§é
ä¿¡ããå€ã®åãå®çŸ©ããŸãã interface Context { userId: number ; setUserId: Dispatch < SetStateAction < number >>; } createContext ã䜿ã£ãŠã³ã³ããã¹ããªããžã§ã¯ããäœæããŸããååŒæ°ã«ã¯å
ã»ã©å®çŸ©ããåãæå®ãã第 1 åŒæ°ã«ã¯åæå€ãäžããŸãã const AuthContext = createContext < Context >( { userId: -1 , setUserId: () => {} , } ); 次㫠Context ã® Provider ãäœæããŸãã Provider ã® value ããããã£ã«ã³ã³ããã¹ãã§é
ä¿¡ããå€ãæå®ããŸãã const AuthProvider: React.FC = ( { children } ) => { // ã³ã³ããã¹ãã§é
ä¿¡ããå€ const [ userId , setUserId ] = useState < number >( -1 ); return ( < AuthContext.Provider value = {{ userId , setUserId }} > { children } < /AuthContext.Provider > ); } ; // ã³ã³ããã¹ããªããžã§ã¯ããšProviderãexportãã export { AuthContext , AuthProvider } ; createContext ã§äœæãã AuthContext ãš AuthProvider ãå€éšã«å
¬éïŒexportïŒããããšã§ã³ã³ããã¹ããå©çšããããããŠããŸãã â¶ïž ã¯ãªãã¯ããŠã³ãŒãå
šæãèŠã // contexts/auth.tsx import React , { createContext , Dispatch , SetStateAction , useState , } from "react" ; interface Context { userId: number ; setUserId: Dispatch < SetStateAction < number >>; } const AuthContext = createContext < Context >( { userId: -1 , setUserId: () => {} , } ); const AuthProvider: React.FC = ( { children } ) => { const [ userId , setUserId ] = useState < number >( -1 ); return ( < AuthContext.Provider value = {{ userId , setUserId }} > { children } < /AuthContext.Provider > ); } ; // ã³ã³ããã¹ããªããžã§ã¯ããšProviderãexportãã export { AuthContext , AuthProvider } ; äœæãã AuthProvider ã App.tsx ã«èšè¿°ããŸãã ããã«ããã¢ããªã±ãŒã·ã§ã³å
ã®ã©ã® ã³ã³ããŒãã³ã ã§ã useContext ã䜿ã£ãŠ AuthContext ããå€ã賌èªã§ããŸãã // App.tsx import React from "react" ; import { AuthProvider } from "./contexts/auth" ; import LoginForm from "./components/LoginForm" ; import ToDoList from "./components/ToDoList" ; export default function App () : JSX. Element { return ( < AuthProvider > < div style = {{ padding: "8px" , textAlign: "center" }} > < LoginForm / > < ToDoList / > < /div > < /AuthProvider > ); } å®éã« LoginForm ãš ToDoList ã³ã³ããŒãã³ã ã§ã³ã³ããã¹ãã䜿ã£ãŠã¿ãŸãããã ãŸã LoginForm ã³ã³ããŒãã³ã å
ã§ãã©ãŒã éä¿¡æã«ã³ã³ããã¹ãã® userId ãæŽæ°ããŠã¿ãŸãã // AuthContextããuserIdãæŽæ°ãã颿°setUserIdãè³Œèª const { setUserId } = useContext ( AuthContext ); // formèŠçŽ ã®submitã€ãã³ããåŠçãã颿° const submitHandler = ( e: FormEvent < HTMLFormElement >) => { e.preventDefault (); console .log ( "ãã°ã€ã³ãã¿ã³æŒäž" , formData ); // AuthContextã§é
ä¿¡ãããå€userIdãæŽæ° setUserId ( 1 ); } ; 次㫠ToDoList ã³ã³ããŒãã³ã ã§ã³ã³ããã¹ããã userId ã賌èªããŸãã ãã㊠useEffect ã§ userId ã®ç¶æ
ãç£èŠããåæå€ïŒ-1ïŒã§ãªãå Žåã«å€éš API ãããªãœãŒã¹ãååŸããããã«ããŸãã // AuthContextããuserIdãè³Œèª const { userId } = useContext ( AuthContext ); useEffect (() => { const f = async () => { const res: Response = await fetch ( "https://jsonplaceholder.typicode.com/todos" ); const json: ToDo [] = await res.json (); setToDos ( json ); } ; // userId ãåæå€ã§ãªãå Žåã«å€éšAPIã³ãŒã« if ( userId !== -1 ) { f (); } } , [ userId ] ); 以äžã§ãã°ã€ã³ãã©ãŒã ã®ãã°ã€ã³ãã¿ã³ãæŒäžããããšã§ AuthContext ã® userId ãæŽæ°ãããã®å€æŽãæ€ç¥ã㊠ToDo ãªã¹ãã®æ
å ±ãå€éš API ããååŸããåŠçãå®çŸã§ããŸãã useReducer useReducer 㯠useState ãããè€éãªç¶æ
ã管çããããã®ããã¯ã§ãã å
¬åŒããã¥ã¡ã³ãã§ã¯ã useState ã®ä»£æ¿åããšããŠäœçœ®ã¥ããããŠããŸãã useReducer ã䜿ãããã®åºæ¬çãªã³ãŒãã¯ä»¥äžã®ãšããã§ãã // `useState`ã®ä»£æ¿åãè¿ãå€ã¯stateã®å€æ°ãšstateãæŽæ°ããããã®Dispatch颿° const [ state , dispatch ] = useReducer ( reducer , initialArg , init ); useReducer ãçè§£ããããã«ã¯ 4 ã€ã®èŠçŽ ãçè§£ããå¿
èŠããããŸãã State âŠ ç¶æ
Reducer ⊠State ãæŽæ°ããããã®é¢æ° Action ⊠State ãæŽæ°ããã®ã«å¿
èŠãªããŒã¿ Dispatch ⊠Action ã Reducer ã«å±ãã颿° ãã® 4 ã€ã®èŠçŽ ã¯å³ã®ãããªé¢ä¿ã«ãªããŸãã useReducer ã䜿ã ãŠãŒã¹ã±ãŒã¹ ã¢ããªã±ãŒã·ã§ã³éçºãé²ããŠãããšåŠçãè€éã«ãªãã«ã€ããŠã管çããªããã°ãªããªãç¶æ
ïŒstateïŒãå¢ããŠãããŸãã ãŸãå®éã«ã¯ãçžäºã«é¢é£ããç¶æ
ãæŽæ°ããªããã°ãªããªãå Žé¢ãå¢ããŸãã ãã®ãããªå Žé¢ã§åãçºæ®ããã®ã useReducer ããã¯ã§ãã äŸãã°ãå
ã»ã©ã® useEffect ããã¯ã§ã䜿ã£ãå€éš API ããã®ãªãœãŒã¹ååŸãäŸã«èããŠã¿ãŸãããã å€éšãªãœãŒã¹ã®ååŸã§ã¯ååŸãŸã§ã«æéãèŠããŸãã®ã§èªã¿èŸŒã¿äžãåŠãã isLoading ã®ãããªåœ¢ã§ç¶æ
管çããå¿
èŠãããã§ãããã ããã«ããŒã¿ååŸæã®ãšã©ãŒãã³ããªã³ã°ãèãããšãšã©ãŒãçºçãããã error ã®ãããªå€æ°ã§ç¶æ
管çããå¿
èŠããããŸãã ãããã useState ããã¯ã§ç®¡çããå Žåã¯ä»¥äžã®ããã«ãªããŸãã const [ todos , setToDos ] = useState < ToDo [] >( [] ); const [ isLoading , setIsLoading ] = useState < boolean >( true ); const [ error , setError ] = useState < boolean >( false ); ãã®ããã«è€æ°ã®å€ã«é¢é£ããç¶æ
ã管çããå Žé¢ã§ useReducer ã䜿ãããšãèããŸãã ãŸã useReducer ã§ç®¡çããç¶æ
ã®åãšãã®ç¶æ
ã®åæå€ãå®çŸ©ããŸãã // 管çããç¶æ
ã®å interface State { todoItems: ToDo [] ; isLoading: boolean ; error: boolean ; } // ç¶æ
ã®åæå€ const initState: State = { todoItems: [] , isLoading: true , error: false , } ; 次ã«ç¶æ
ãæŽæ°ããããã®ããŒã¿ãšãªãã¢ã¯ã·ã§ã³ã®åãå®çŸ©ããŸãã ä»åã¯ç¶æ
ãæŽæ°ããæäœãšããŠä»¥äžã® 2 çš®é¡ãèããŸãã SET_TODOS ⊠ToDo ãªã¹ãã«ã¢ã€ãã ãã»ããããæäœãã¢ã¯ã·ã§ã³ã¯ ToDo ãªã¹ãã«ã»ããããããŒã¿ãå«ãã SET_ERROR ⊠ãšã©ãŒãçºçããéã«ãš ã©ãŒãã© ã°ã True ã«ããæäœãã¢ã¯ã·ã§ã³ã¯ããŒã¿ãæããªãã ããããåã«èµ·ãããŸãã // ã¢ã¯ã·ã§ã³ã®çš®é¡ type ActionType = "SET_TODOS" | "SET_ERROR" ; // ã¢ã¯ã·ã§ã³ã®å interface Action { type : ActionType ; payload?: ToDo [] ; } äžã§å®çŸ©ããåã䜿ã£ãŠ reducer 颿°ãäœæããŸãã import { Reducer } from "react" ; const reducer: Reducer < State , Action > = ( state , action ) => { switch ( action. type) { case "SET_TODOS" : if ( ! action.payload ) { // payloadãå«ãŸããŠããªããã°ãšã©ãŒæ±ãã«ãã return { ...state , error: true , isLoading: false , } ; } return { ...state , ...action.payload , isLoading: false , } ; case "SET_ERROR" : return { ...state , error: true , isLoading: false , } ; } } ; ãã® reducer 颿°ãšç¶æ
ã®åæå€ã䜿ã£ãŠ useReducer ã宣èšããŸãã const [{ todoItems , error , isLoading } , dispatch ] = useReducer ( reducer , initState ); ãããŠå
çšã® useEffect å
ã§ç¶æ
ãæŽæ°ããŠããéšåã dispatch ã«ã¢ã¯ã·ã§ã³ãæž¡ãããšã§ç¶æ
ãæŽæ°ããããã«æžãæããŸãã useEffect (() => { const f = async () => { try { const res: Response = await fetch ( "https://jsonplaceholder.typicode.com/todos" ); const json: ToDo [] = await res.json (); dispatch ( { type : "SET_TODOS" , payload: { todoItems: json } } ); } catch ( e ) { console .log ( e ); dispatch ( { type : "SET_ERROR" } ); } } ; f (); } , [] ); ãã®äŸã ãšããšããšããããŸã§è€éãªç¶æ
管çã§ã¯ãªãã£ãããã useReducer ã䜿ã£ãèšè¿°ãåé·ã ãšæãããããããŸããã ã©ã®ã¿ã€ãã³ã°ã§ useReducer ã䜿ãã®ãã¯ãå人/ããŒã 次第ã§ã¯ãããŸãããããŸã䜿ãããšã§ç¶æ
管çãããããããã§ããŸãã â¶ïž ã¯ãªãã¯ããŠã³ãŒãå
šæãèŠã // components/ToDoList.tsx import React , { Reducer , useEffect , useReducer } from "react" ; interface ToDo { id: number ; userId: number ; title: string ; completed: boolean ; } interface State { todoItems: ToDo [] ; isLoading: boolean ; error: boolean ; } const initState: State = { todoItems: [] , isLoading: true , error: false , } ; type ActionType = "SET_TODOS" | "SET_ERROR" ; interface Action { type : ActionType ; payload?: Partial < State >; } const reducer: Reducer < State , Action > = ( state , action ) => { switch ( action. type) { case "SET_TODOS" : if ( ! action.payload ) { // payloadãå«ãŸããŠããªããã°ãšã©ãŒæ±ãã«ãã return { ...state , error: true , isLoading: false , } ; } return { ...state , ...action.payload.todoItems , isLoading: false , } ; case "SET_ERROR" : return { ...state , error: true , isLoading: false , } ; } } ; export default function ToDoList () : JSX. Element { const [{ todoItems , error , isLoading } , dispatch ] = useReducer ( reducer , initState ); useEffect (() => { const f = async () => { try { const res: Response = await fetch ( "https://jsonplaceholder.typicode.com/todos" ); const json: ToDo [] = await res.json (); dispatch ( { type : "SET_TODOS" , payload: { todoItems: json } } ); } catch ( e ) { console .log ( e ); dispatch ( { type : "SET_ERROR" } ); } } ; f (); } , [] ); return ( <> { isLoading ? ( < p > ããŒãäžã§ã⊠< /p > ) : error ? ( < p > ãšã©ãŒãçºçããŸããã < /p > ) : ( < div style = {{ textAlign: "left" }} > { todoItems.map (( todoItem ) => ( < div key = { todoItem.id } style = {{ width: "250px" , border: "solid" , margin: "8px" , padding: "8px" , }} > < h4 > { todoItem.title } < /h4 > < p style = {{ textAlign: "right" }} > { todoItem.completed ? "â
å®äº" : "æªå®æœ" } < /p > < /div > )) } < /div > ) } < / > ); } useMemo useMemo ã¯é¢æ°ã®è¿ãå€ãã¡ã¢åããããã¯ã§ãã ã¡ã¢åã¯ããã°ã©ã ã®æé©åææ³ã® 1 ã€ã§ãèšç®çµæãåå©çšããããã«ä¿æããŠãåèšç®ãé²ããã®ã§ãã ãã®ãã useMemo ã¯æé©åã®ããã®ããã¯ãšããäœçœ®ä»ãã§ãã useMemo ã䜿ãããã®åºæ¬çãªã³ãŒãã¯ä»¥äžã®ãšããã§ãã // è¿ãå€ã¯é¢æ°ã®èšç®çµæãã¡ã¢åããå€ // 第2åŒæ°ã®äŸåé
åã«å«ãŸããå€ã倿Žãããæã«åèšç®ããã const memoizedValue = useMemo < T >(() => computeExpensiveValue ( a , b ), [ a , b ] ); useMemo ã䜿ã ãŠãŒã¹ã±ãŒã¹ åºæ¬çã«ã¯æé©åã®ããã®ããã¯ã§ãããäŸãã°é
åãä¿æãã state ã§é
åãèµ°æ»ããåŠçãé »ç¹ã«å¿
èŠãªå Žåãªã©ã«åœ¹ç«ã¡ãŸãã ToDo ãªã¹ãã®äŸã§ãäžèЧããå®äºæžã¿ã®ã¢ã€ãã ã useMemo ã«ãã£ãŠååŸããããšãèããŠã¿ãŸãããã const [ todoItems , setToDos ] = useState < ToDo [] >( [] ); const completedItems = useMemo < ToDo [] >(() => { return todoItems.filter (( todoItem ) => todoItem.completed ); } , [ todos ] ); useMemo ã§ã¯äŸåé
åã«æž¡ããã state ãæŽæ°ãããæã«ã¡ã¢åããŠããå€ãåèšç®ããŸãã useCallback useCallback ã¯é¢æ°ãã¡ã¢åããããã¯ã§ãã useCallback ã¯æé©åã®ããã®ããã¯ãšããäœçœ®ä»ãã§ãã ãã㊠useCallback ãå©çšããå Žåã¯ãåºæ¬çã« React.memo ãšäœµçšããå¿
èŠããããŸãã React.memo ãš useCallback useCallback ã®è©±ãããåã«ã React.memo ã«ã€ããŠç°¡åã«èª¬æããŸãã React ã®æäžäœ API - React.memo, https://ja.reactjs.org/docs/react-api.html#reactmemo ãã§ã«è¿°ã¹ãéããReact ã§ã¯èŠª ã³ã³ããŒãã³ã ããå ã³ã³ããŒãã³ã ã« props ãéããŠããŒã¿ãæž¡ããŸãã éåžžã§ã¯ãå³äžã®ç¹ç·ã§ç€ºããå ã³ã³ããŒãã³ã ã¯èŠª ã³ã³ããŒãã³ã ãåæç»ãããã¿ã€ãã³ã°ã§åžžã«åæç»ãããŸãã React.memo ã¯ãã®èŠª ã³ã³ããŒãã³ã ãåæç»ãããã¿ã€ãã³ã°ã§ã®å ã³ã³ããŒãã³ã ã®åæç»ãæé©åãããã®ã§ãã React.memo ã§ã¯å ã³ã³ããŒãã³ã ã«ãããŠã芪 ã³ã³ããŒãã³ã ããåãåã props ãåæç»åã® props ãšç䟡ã§ããã°ãåæç»ãã¹ãããããŸããã€ãŸã芪 ã³ã³ããŒãã³ã ããå ã³ã³ããŒãã³ã ã«æž¡ã props ãšãã®ç䟡æ§ãéèŠã«ãªããŸãã useCallback 㯠props ã«æž¡ã颿°ãç䟡ã§ããããšãä¿èšŒããããã®ããã¯ã§ãã useCallback ã䜿ãããã®åºæ¬çãªã³ãŒãã¯ä»¥äžã®ãšããã§ãã // è¿ãå€ã¯ã¡ã¢åããã颿° // 第2åŒæ°ã®äŸåé
åã«å«ãŸããå€ã倿Žãããæã«åèšç®ããã const memoizedCallback = useCallback (() => { doSomething ( a , b ); } , [ a , b ] ); ããã«ããå ã³ã³ããŒãã³ã ã§ã¯ãprops åãåã£ã颿°ã useCallback ã®ç¬¬2åŒæ°ã®äŸåé
åã«å«ãŸããå€ã倿ŽãããŠããªãéãã¯ç䟡ãªãã®ãšããŠæ±ããŸãã useCallback ã䜿ã ãŠãŒã¹ã±ãŒã¹ ãããŸã§èª¬æããéãã useCallback ã¯æé©åã®æµãã§ãå ã³ã³ããŒãã³ã ã® props ã«é¢æ°ãæž¡ãå¿
èŠãçããéã«å©çšããŸãã React.memo ã¯äœ¿çšããŸããããprops ã«é¢æ°ãæž¡ãå Žé¢ãå
ã»ã©ã®ãã°ã€ã³ãã©ãŒã ã®äŸã§èŠãŠã¿ãŸãããã ãŸããã©ãŒã ã®ç¶æ
ã useState ã䜿ã£ãŠå®çŸ©ããŠããŸããã const [ formData , setFormData ] = useState < SampleLoginForm >( { userId: "" , password: "" , } ); ãã㊠input èŠçŽ ã® onChange ããããã£ã«é¢æ°ãèšè¿°ã formData ã®å€ãæŽæ°ããŠããŸããã < input id = "userId" type= "text" name = "userId" placeholder = "ãŠãŒã¶ãŒID" value = { formData.userId } onChange = { ( e ) => setFormData ( { ...formData , userId: e.target.value } ) } / > ãã® onChange ããããã£ã«æž¡ã颿°ã useCallback ã§èšè¿°ãããšä»¥äžã®ããã«ãªããŸãã // inputã¿ã°ã®onChangeã€ãã³ããåŠçãã颿° const onChangeHandler = useCallback (( e: ChangeEvent < HTMLInputElement >) => { setFormData (( prev: SampleLoginForm ) => { return { ...prev , [ e.target.name ] : e.target.value } ; } ); } , [] ); useCallback ã§ã¯ç¬¬ 2 åŒæ°ã®äŸåé
åã«å«ãŸããå€ã倿Žãããã¿ã€ãã³ã°ã§å床ã¡ã¢åããããããäŸåé
åã«å«ãŸããå€ãå°ãªããªãããã«æèããå¿
èŠããããŸãã ãã®äŸã§ã¯ãæŽæ°æã« formData ãåç
§ããã setFormData 颿°å
ã§çŽåã® formData ã®å€ãåããããšãã£ãŠäŸåé
åã空ã«ãªãããã«ããŠããŸãã ããã«ãã onChangeHandler 颿°ã¯ã¡ã¢åãåãã React.memo ãšäœµçšããæé©åãã§ããŸãã React Hooks ãæ£ãã䜿ãããã« ããã¯ã¯äžèŠãããš JavaScript ã®é¢æ°ã§ãããæ£ãã䜿ãéã«ã¯ãã«ãŒã«ã«åŸãå¿
èŠããããŸãã ç¹ã« useEffect ã useMemo ã useCallback ãšãã£ãäŸåé
åãå«ãããã¯ã®äœ¿çšã§ã¯ãäŸåé¢ä¿ã®æŒãã«ãã£ãŠãã°ãæ··å
¥ããæãããããŸãã ããã¯ãæ£ããå©çšããããã«ãESLint ã® eslint-plugin-react-hooks ãã©ã°ã€ã³ ãå°å
¥ããŠããããšããå§ãã§ãã exhaustive-deps ã«ãŒã«ãæå¹ã«ããã°ãäŸåé
åãæ£ããèšè¿°ãããŠããªãå Žåã«èŠåãåºãããšãã§ããŸãã eslint-plugin-react-hooks https://www.npmjs.com/package/eslint-plugin-react-hooks ãããã« æ¬èšäºã§ã¯ãReact ã§æäŸãããåºæ¬ç㪠React Hooks ããã® ãŠãŒã¹ã±ãŒã¹ ãšãšãã«ç޹ä»ããŸããã ããã§ã¯ç޹ä»ã§ããªãã£ã React Hooks ãã«ã¹ã¿ã ããã¯ããã¹ãæ¹æ³ãªã©ãä»åŸã玹ä»ã§ããã°ãšæããŸãã ææ¥ïŒ12/14ïŒã¯ Toshihiro Nakamura ãããããKotlinã§ããŒã¿ããŒã¹ã¢ã¯ã»ã¹ãã®èšäºãå
¬éãããäºå®ã§ãã ãã¡ãããã²ã芧ãã ããã å·çïŒ @yamada.y ãã¬ãã¥ãŒïŒ @sato.taichi ïŒ Shodo ã§å·çãããŸãã ïŒ