Upgrade to Pro — share decks privately, control downloads, hide ads and more …

3つのNext.jsプロジェクトを 新卒エンジニアと一緒に 開発した話

18c4748985323ef0e69f3436f89fbdb8?s=47 hiroya iizuka
July 30, 2021
170

3つのNext.jsプロジェクトを 新卒エンジニアと一緒に 開発した話

Next.js 開発の話です。

18c4748985323ef0e69f3436f89fbdb8?s=128

hiroya iizuka

July 30, 2021
Tweet

Transcript

  1. 3つのNext.js プロジェクトを 新卒エンジニアと⼀緒に 開発した話 株式会社BeatFit CTO 飯塚 浩也

  2. 医師 循環器内科、総合診療医と して、⼤学 - 地⽅の病院を 8 年間勤務。 エンジニア 株式会社BeatFit にエンジニ

    アとして⼊社。 [ ⽇課] Codewars, LeetCode Hack The Box Type Challenge
  3. 今⽇のお話 3 つのNext.js のプロジェクト どんな、役割分担をしたか? どんな、技術選定をしたか? 新⼊社員研修の参考になれば 🙏

  4. サインアップまでの流れ

  5. サインアップ (Web) Web (Next.js)

  6. 1 つめのプロジェクト 実務未経験インターン⽣(23 歳) 期限は1 ヶ⽉間 ⼤型ジム会員のactivate site 2021. 2-3

  7. 概要 User 登録

  8. 技術選定 HTTP client: axios css frameWork: tailwind test: jest, Cypress

    Next.js + Typescript
  9. 役割分担 → メイン実装 → リファクタリング

  10. 関⼼の分離 凝集度 状態管理 リファクタリング: 意識したこと

  11. 初期状態 😓 const Home : NextPage = () => {

    const [number, setNumber] = useState("")) const [year, selectYear] = useState("1980"); const [month, selectMonth] = useState("1"); const [day, selectDay] = useState("1"); useEffect(() => { ・・・ }, []); ・・・ return( <div> <div> </div> </div> } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
  12. const Home : NextPage = () => { const [year,

    selectYear] = useState("1980"); const [month, selectMonth] = useState("1"); const [day, selectDay] = useState("1"); useEffect(() => { ・・・ }, []); ・・・ return( <div> <div> </div> </div> } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 } const Container = () => { 1 const [year, selectYear] = useState("1980"); 2 const [month, selectMonth] = useState("1"); 3 const [day, selectDay] = useState("1"); 4 5 6 useEffect(() => { 7 ... 8 }, []); 9 10 return( 11 <Component 12 year = {year} 13 month = {month} 14 day = {day} 15 /> 16 } 17 } 18 19 const Component = ({ 20 year, 21 month, 22 day 23 }: Props) => { 24 return ( 25 <div> 26 27 </div> 28 ) 29 30 Presentation Domain Separation
  13. const Home : NextPage = () => { const [year,

    selectYear] = useState("1980"); const [month, selectMonth] = useState("1"); const [day, selectDay] = useState("1"); useEffect(() => { ・・・ }, []); ・・・ return( <div> <div> </div> </div> } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 } const Container = () => { 1 const [year, selectYear] = useState("1980"); 2 const [month, selectMonth] = useState("1"); 3 const [day, selectDay] = useState("1"); 4 5 6 useEffect(() => { 7 ... 8 }, []); 9 10 return( 11 <Component 12 year = {year} 13 month = {month} 14 day = {day} 15 /> 16 } 17 } 18 19 const Component = ({ 20 year, 21 month, 22 day 23 }: Props) => { 24 return ( 25 <div> 26 27 </div> 28 ) 29 30 const Container = () => { const [year, selectYear] = useState("1980"); const [month, selectMonth] = useState("1"); const [day, selectDay] = useState("1"); useEffect(() => { ... }, []); return( <Component year = {year} month = {month} day = {day} /> } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const Component = ({ 20 year, 21 month, 22 day 23 }: Props) => { 24 return ( 25 <div> 26 27 </div> 28 ) 29 } 30 Presentation Domain Separation
  14. const Home : NextPage = () => { const [year,

    selectYear] = useState("1980"); const [month, selectMonth] = useState("1"); const [day, selectDay] = useState("1"); useEffect(() => { ・・・ }, []); ・・・ return( <div> <div> </div> </div> } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 } const Container = () => { 1 const [year, selectYear] = useState("1980"); 2 const [month, selectMonth] = useState("1"); 3 const [day, selectDay] = useState("1"); 4 5 6 useEffect(() => { 7 ... 8 }, []); 9 10 return( 11 <Component 12 year = {year} 13 month = {month} 14 day = {day} 15 /> 16 } 17 } 18 19 const Component = ({ 20 year, 21 month, 22 day 23 }: Props) => { 24 return ( 25 <div> 26 27 </div> 28 ) 29 30 const Container = () => { const [year, selectYear] = useState("1980"); const [month, selectMonth] = useState("1"); const [day, selectDay] = useState("1"); useEffect(() => { ... }, []); return( <Component year = {year} month = {month} day = {day} /> } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const Component = ({ 20 year, 21 month, 22 day 23 }: Props) => { 24 return ( 25 <div> 26 27 </div> 28 ) 29 } 30 const Component = ({ year, month, day }: Props) => { return ( <div> </div> ) } const Container = () => { 1 const [year, selectYear] = useState("1980"); 2 const [month, selectMonth] = useState("1"); 3 const [day, selectDay] = useState("1"); 4 5 6 useEffect(() => { 7 ... 8 }, []); 9 10 return( 11 <Component 12 year = {year} 13 month = {month} 14 day = {day} 15 /> 16 } 17 } 18 19 20 21 22 23 24 25 26 27 28 29 30 Presentation Domain Separation
  15. const Home : NextPage = () => { const [year,

    selectYear] = useState("1980"); const [month, selectMonth] = useState("1"); const [day, selectDay] = useState("1"); useEffect(() => { ・・・ }, []); ・・・ return( <div> <div> </div> </div> } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 } const Container = () => { 1 const [year, selectYear] = useState("1980"); 2 const [month, selectMonth] = useState("1"); 3 const [day, selectDay] = useState("1"); 4 5 6 useEffect(() => { 7 ... 8 }, []); 9 10 return( 11 <Component 12 year = {year} 13 month = {month} 14 day = {day} 15 /> 16 } 17 } 18 19 const Component = ({ 20 year, 21 month, 22 day 23 }: Props) => { 24 return ( 25 <div> 26 27 </div> 28 ) 29 30 const Container = () => { const [year, selectYear] = useState("1980"); const [month, selectMonth] = useState("1"); const [day, selectDay] = useState("1"); useEffect(() => { ... }, []); return( <Component year = {year} month = {month} day = {day} /> } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const Component = ({ 20 year, 21 month, 22 day 23 }: Props) => { 24 return ( 25 <div> 26 27 </div> 28 ) 29 } 30 const Component = ({ year, month, day }: Props) => { return ( <div> </div> ) } const Container = () => { 1 const [year, selectYear] = useState("1980"); 2 const [month, selectMonth] = useState("1"); 3 const [day, selectDay] = useState("1"); 4 5 6 useEffect(() => { 7 ... 8 }, []); 9 10 return( 11 <Component 12 year = {year} 13 month = {month} 14 day = {day} 15 /> 16 } 17 } 18 19 20 21 22 23 24 25 26 27 28 29 30 const Container = () => { const [year, selectYear] = useState("1980"); const [month, selectMonth] = useState("1"); const [day, selectDay] = useState("1"); useEffect(() => { ... }, []); return( <Component year = {year} month = {month} day = {day} /> } } const Component = ({ year, month, day }: Props) => { return ( <div> </div> ) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 Presentation Domain Separation
  16. 低すぎる凝集度・・・ 😰

  17. 凝集度up 😋 各component が 1 つの機能( 責任) を 持つように分離

  18. 状態管理は、useContext を使⽤

  19. 2 つめのプロジェクト 新卒⼊社! 期限は1 ヶ⽉間 ⼤型ジム提携 2021. 4-5

  20. 役割分担 → メイン実装 → レビュー

  21. 技術選定 HTTP client: axios + CSS frameWork: tailwind test: jest,

    Cypress Next.js + Typescript aspida new!
  22. 従来 const loadProducts = async(): Promise<Product[]> => { const response

    = await fetch('https://api.mysite.com/products') const products: unknown = await response.json() if (!isArrayOfProducts(products)) { throw new TypeError('Received malformed products API response') } return products } const isArrayOfProducts = (obj: unknown): obj is Product[] => { return Array.isArray(obj) && obj.every(isProduct) } const isProduct = (obj: unknown): obj is Product => { return obj != null && typeof (obj as Product).id === 'string' } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Typescript の ユーザー定義型ガード
  23. 従来 const loadProducts = async(): Promise<Product[]> => { const response

    = await fetch('https://api.mysite.com/products') const products: unknown = await response.json() if (!isArrayOfProducts(products)) { throw new TypeError('Received malformed products API response') } return products } const isArrayOfProducts = (obj: unknown): obj is Product[] => { return Array.isArray(obj) && obj.every(isProduct) } const isProduct = (obj: unknown): obj is Product => { return obj != null && typeof (obj as Product).id === 'string' } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const response = await fetch('https://api.mysite.com/products') const products: unknown = await response.json() const loadProducts = async(): Promise<Product[]> => { 1 2 3 4 if (!isArrayOfProducts(products)) { 5 throw new TypeError('Received malformed products API response') 6 } 7 return products 8 } 9 10 const isArrayOfProducts = (obj: unknown): obj is Product[] => { 11 return Array.isArray(obj) && obj.every(isProduct) 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 Typescript の ユーザー定義型ガード
  24. 従来 const loadProducts = async(): Promise<Product[]> => { const response

    = await fetch('https://api.mysite.com/products') const products: unknown = await response.json() if (!isArrayOfProducts(products)) { throw new TypeError('Received malformed products API response') } return products } const isArrayOfProducts = (obj: unknown): obj is Product[] => { return Array.isArray(obj) && obj.every(isProduct) } const isProduct = (obj: unknown): obj is Product => { return obj != null && typeof (obj as Product).id === 'string' } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const response = await fetch('https://api.mysite.com/products') const products: unknown = await response.json() const loadProducts = async(): Promise<Product[]> => { 1 2 3 4 if (!isArrayOfProducts(products)) { 5 throw new TypeError('Received malformed products API response') 6 } 7 return products 8 } 9 10 const isArrayOfProducts = (obj: unknown): obj is Product[] => { 11 return Array.isArray(obj) && obj.every(isProduct) 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 if (!isArrayOfProducts(products)) { throw new TypeError('Received malformed products API response') } return products const loadProducts = async(): Promise<Product[]> => { 1 const response = await fetch('https://api.mysite.com/products') 2 const products: unknown = await response.json() 3 4 5 6 7 8 } 9 10 const isArrayOfProducts = (obj: unknown): obj is Product[] => { 11 return Array.isArray(obj) && obj.every(isProduct) 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 Typescript の ユーザー定義型ガード
  25. 従来 const loadProducts = async(): Promise<Product[]> => { const response

    = await fetch('https://api.mysite.com/products') const products: unknown = await response.json() if (!isArrayOfProducts(products)) { throw new TypeError('Received malformed products API response') } return products } const isArrayOfProducts = (obj: unknown): obj is Product[] => { return Array.isArray(obj) && obj.every(isProduct) } const isProduct = (obj: unknown): obj is Product => { return obj != null && typeof (obj as Product).id === 'string' } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const response = await fetch('https://api.mysite.com/products') const products: unknown = await response.json() const loadProducts = async(): Promise<Product[]> => { 1 2 3 4 if (!isArrayOfProducts(products)) { 5 throw new TypeError('Received malformed products API response') 6 } 7 return products 8 } 9 10 const isArrayOfProducts = (obj: unknown): obj is Product[] => { 11 return Array.isArray(obj) && obj.every(isProduct) 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 if (!isArrayOfProducts(products)) { throw new TypeError('Received malformed products API response') } return products const loadProducts = async(): Promise<Product[]> => { 1 const response = await fetch('https://api.mysite.com/products') 2 const products: unknown = await response.json() 3 4 5 6 7 8 } 9 10 const isArrayOfProducts = (obj: unknown): obj is Product[] => { 11 return Array.isArray(obj) && obj.every(isProduct) 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 const isArrayOfProducts = (obj: unknown): obj is Product[] => { return Array.isArray(obj) && obj.every(isProduct) } const isProduct = (obj: unknown): obj is Product => { return obj != null && typeof (obj as Product).id === 'string' } const loadProducts = async(): Promise<Product[]> => { 1 const response = await fetch('https://api.mysite.com/products') 2 const products: unknown = await response.json() 3 4 if (!isArrayOfProducts(products)) { 5 throw new TypeError('Received malformed products API response') 6 } 7 return products 8 } 9 10 11 12 13 14 15 16 17 Typescript の ユーザー定義型ガード
  26. 従来 const loadProducts = async(): Promise<Product[]> => { const response

    = await fetch('https://api.mysite.com/products') const products: unknown = await response.json() if (!isArrayOfProducts(products)) { throw new TypeError('Received malformed products API response') } return products } const isArrayOfProducts = (obj: unknown): obj is Product[] => { return Array.isArray(obj) && obj.every(isProduct) } const isProduct = (obj: unknown): obj is Product => { return obj != null && typeof (obj as Product).id === 'string' } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const response = await fetch('https://api.mysite.com/products') const products: unknown = await response.json() const loadProducts = async(): Promise<Product[]> => { 1 2 3 4 if (!isArrayOfProducts(products)) { 5 throw new TypeError('Received malformed products API response') 6 } 7 return products 8 } 9 10 const isArrayOfProducts = (obj: unknown): obj is Product[] => { 11 return Array.isArray(obj) && obj.every(isProduct) 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 if (!isArrayOfProducts(products)) { throw new TypeError('Received malformed products API response') } return products const loadProducts = async(): Promise<Product[]> => { 1 const response = await fetch('https://api.mysite.com/products') 2 const products: unknown = await response.json() 3 4 5 6 7 8 } 9 10 const isArrayOfProducts = (obj: unknown): obj is Product[] => { 11 return Array.isArray(obj) && obj.every(isProduct) 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 const isArrayOfProducts = (obj: unknown): obj is Product[] => { return Array.isArray(obj) && obj.every(isProduct) } const isProduct = (obj: unknown): obj is Product => { return obj != null && typeof (obj as Product).id === 'string' } const loadProducts = async(): Promise<Product[]> => { 1 const response = await fetch('https://api.mysite.com/products') 2 const products: unknown = await response.json() 3 4 if (!isArrayOfProducts(products)) { 5 throw new TypeError('Received malformed products API response') 6 } 7 return products 8 } 9 10 11 12 13 14 15 16 17 return Array.isArray(obj) && obj.every(isProduct) const loadProducts = async(): Promise<Product[]> => { 1 const response = await fetch('https://api.mysite.com/products') 2 const products: unknown = await response.json() 3 4 if (!isArrayOfProducts(products)) { 5 throw new TypeError('Received malformed products API response') 6 } 7 return products 8 } 9 10 const isArrayOfProducts = (obj: unknown): obj is Product[] => { 11 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 Typescript の ユーザー定義型ガード
  27. 従来 const loadProducts = async(): Promise<Product[]> => { const response

    = await fetch('https://api.mysite.com/products') const products: unknown = await response.json() if (!isArrayOfProducts(products)) { throw new TypeError('Received malformed products API response') } return products } const isArrayOfProducts = (obj: unknown): obj is Product[] => { return Array.isArray(obj) && obj.every(isProduct) } const isProduct = (obj: unknown): obj is Product => { return obj != null && typeof (obj as Product).id === 'string' } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const response = await fetch('https://api.mysite.com/products') const products: unknown = await response.json() const loadProducts = async(): Promise<Product[]> => { 1 2 3 4 if (!isArrayOfProducts(products)) { 5 throw new TypeError('Received malformed products API response') 6 } 7 return products 8 } 9 10 const isArrayOfProducts = (obj: unknown): obj is Product[] => { 11 return Array.isArray(obj) && obj.every(isProduct) 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 if (!isArrayOfProducts(products)) { throw new TypeError('Received malformed products API response') } return products const loadProducts = async(): Promise<Product[]> => { 1 const response = await fetch('https://api.mysite.com/products') 2 const products: unknown = await response.json() 3 4 5 6 7 8 } 9 10 const isArrayOfProducts = (obj: unknown): obj is Product[] => { 11 return Array.isArray(obj) && obj.every(isProduct) 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 const isArrayOfProducts = (obj: unknown): obj is Product[] => { return Array.isArray(obj) && obj.every(isProduct) } const isProduct = (obj: unknown): obj is Product => { return obj != null && typeof (obj as Product).id === 'string' } const loadProducts = async(): Promise<Product[]> => { 1 const response = await fetch('https://api.mysite.com/products') 2 const products: unknown = await response.json() 3 4 if (!isArrayOfProducts(products)) { 5 throw new TypeError('Received malformed products API response') 6 } 7 return products 8 } 9 10 11 12 13 14 15 16 17 return Array.isArray(obj) && obj.every(isProduct) const loadProducts = async(): Promise<Product[]> => { 1 const response = await fetch('https://api.mysite.com/products') 2 const products: unknown = await response.json() 3 4 if (!isArrayOfProducts(products)) { 5 throw new TypeError('Received malformed products API response') 6 } 7 return products 8 } 9 10 const isArrayOfProducts = (obj: unknown): obj is Product[] => { 11 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 const isArrayOfProducts = (obj: unknown): obj is Product[] => { const loadProducts = async(): Promise<Product[]> => { 1 const response = await fetch('https://api.mysite.com/products') 2 const products: unknown = await response.json() 3 4 if (!isArrayOfProducts(products)) { 5 throw new TypeError('Received malformed products API response') 6 } 7 return products 8 } 9 10 11 return Array.isArray(obj) && obj.every(isProduct) 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 Typescript の ユーザー定義型ガード
  28. 従来 const loadProducts = async(): Promise<Product[]> => { const response

    = await fetch('https://api.mysite.com/products') const products: unknown = await response.json() if (!isArrayOfProducts(products)) { throw new TypeError('Received malformed products API response') } return products } const isArrayOfProducts = (obj: unknown): obj is Product[] => { return Array.isArray(obj) && obj.every(isProduct) } const isProduct = (obj: unknown): obj is Product => { return obj != null && typeof (obj as Product).id === 'string' } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const response = await fetch('https://api.mysite.com/products') const products: unknown = await response.json() const loadProducts = async(): Promise<Product[]> => { 1 2 3 4 if (!isArrayOfProducts(products)) { 5 throw new TypeError('Received malformed products API response') 6 } 7 return products 8 } 9 10 const isArrayOfProducts = (obj: unknown): obj is Product[] => { 11 return Array.isArray(obj) && obj.every(isProduct) 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 if (!isArrayOfProducts(products)) { throw new TypeError('Received malformed products API response') } return products const loadProducts = async(): Promise<Product[]> => { 1 const response = await fetch('https://api.mysite.com/products') 2 const products: unknown = await response.json() 3 4 5 6 7 8 } 9 10 const isArrayOfProducts = (obj: unknown): obj is Product[] => { 11 return Array.isArray(obj) && obj.every(isProduct) 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 const isArrayOfProducts = (obj: unknown): obj is Product[] => { return Array.isArray(obj) && obj.every(isProduct) } const isProduct = (obj: unknown): obj is Product => { return obj != null && typeof (obj as Product).id === 'string' } const loadProducts = async(): Promise<Product[]> => { 1 const response = await fetch('https://api.mysite.com/products') 2 const products: unknown = await response.json() 3 4 if (!isArrayOfProducts(products)) { 5 throw new TypeError('Received malformed products API response') 6 } 7 return products 8 } 9 10 11 12 13 14 15 16 17 return Array.isArray(obj) && obj.every(isProduct) const loadProducts = async(): Promise<Product[]> => { 1 const response = await fetch('https://api.mysite.com/products') 2 const products: unknown = await response.json() 3 4 if (!isArrayOfProducts(products)) { 5 throw new TypeError('Received malformed products API response') 6 } 7 return products 8 } 9 10 const isArrayOfProducts = (obj: unknown): obj is Product[] => { 11 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 const isArrayOfProducts = (obj: unknown): obj is Product[] => { const loadProducts = async(): Promise<Product[]> => { 1 const response = await fetch('https://api.mysite.com/products') 2 const products: unknown = await response.json() 3 4 if (!isArrayOfProducts(products)) { 5 throw new TypeError('Received malformed products API response') 6 } 7 return products 8 } 9 10 11 return Array.isArray(obj) && obj.every(isProduct) 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 return products const loadProducts = async(): Promise<Product[]> => { 1 const response = await fetch('https://api.mysite.com/products') 2 const products: unknown = await response.json() 3 4 if (!isArrayOfProducts(products)) { 5 throw new TypeError('Received malformed products API response') 6 } 7 8 } 9 10 const isArrayOfProducts = (obj: unknown): obj is Product[] => { 11 return Array.isArray(obj) && obj.every(isProduct) 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 Typescript の ユーザー定義型ガード
  29. 従来 const loadProducts = async(): Promise<Product[]> => { const response

    = await fetch('https://api.mysite.com/products') const products: unknown = await response.json() if (!isArrayOfProducts(products)) { throw new TypeError('Received malformed products API response') } return products } const isArrayOfProducts = (obj: unknown): obj is Product[] => { return Array.isArray(obj) && obj.every(isProduct) } const isProduct = (obj: unknown): obj is Product => { return obj != null && typeof (obj as Product).id === 'string' } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const response = await fetch('https://api.mysite.com/products') const products: unknown = await response.json() const loadProducts = async(): Promise<Product[]> => { 1 2 3 4 if (!isArrayOfProducts(products)) { 5 throw new TypeError('Received malformed products API response') 6 } 7 return products 8 } 9 10 const isArrayOfProducts = (obj: unknown): obj is Product[] => { 11 return Array.isArray(obj) && obj.every(isProduct) 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 if (!isArrayOfProducts(products)) { throw new TypeError('Received malformed products API response') } return products const loadProducts = async(): Promise<Product[]> => { 1 const response = await fetch('https://api.mysite.com/products') 2 const products: unknown = await response.json() 3 4 5 6 7 8 } 9 10 const isArrayOfProducts = (obj: unknown): obj is Product[] => { 11 return Array.isArray(obj) && obj.every(isProduct) 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 const isArrayOfProducts = (obj: unknown): obj is Product[] => { return Array.isArray(obj) && obj.every(isProduct) } const isProduct = (obj: unknown): obj is Product => { return obj != null && typeof (obj as Product).id === 'string' } const loadProducts = async(): Promise<Product[]> => { 1 const response = await fetch('https://api.mysite.com/products') 2 const products: unknown = await response.json() 3 4 if (!isArrayOfProducts(products)) { 5 throw new TypeError('Received malformed products API response') 6 } 7 return products 8 } 9 10 11 12 13 14 15 16 17 return Array.isArray(obj) && obj.every(isProduct) const loadProducts = async(): Promise<Product[]> => { 1 const response = await fetch('https://api.mysite.com/products') 2 const products: unknown = await response.json() 3 4 if (!isArrayOfProducts(products)) { 5 throw new TypeError('Received malformed products API response') 6 } 7 return products 8 } 9 10 const isArrayOfProducts = (obj: unknown): obj is Product[] => { 11 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 const isArrayOfProducts = (obj: unknown): obj is Product[] => { const loadProducts = async(): Promise<Product[]> => { 1 const response = await fetch('https://api.mysite.com/products') 2 const products: unknown = await response.json() 3 4 if (!isArrayOfProducts(products)) { 5 throw new TypeError('Received malformed products API response') 6 } 7 return products 8 } 9 10 11 return Array.isArray(obj) && obj.every(isProduct) 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 return products const loadProducts = async(): Promise<Product[]> => { 1 const response = await fetch('https://api.mysite.com/products') 2 const products: unknown = await response.json() 3 4 if (!isArrayOfProducts(products)) { 5 throw new TypeError('Received malformed products API response') 6 } 7 8 } 9 10 const isArrayOfProducts = (obj: unknown): obj is Product[] => { 11 return Array.isArray(obj) && obj.every(isProduct) 12 } 13 14 const isProduct = (obj: unknown): obj is Product => { 15 return obj != null && typeof (obj as Product).id === 'string' 16 } 17 const loadProducts = async(): Promise<Product[]> => { const response = await fetch('https://api.mysite.com/products') const products: unknown = await response.json() if (!isArrayOfProducts(products)) { throw new TypeError('Received malformed products API response') } return products } const isArrayOfProducts = (obj: unknown): obj is Product[] => { return Array.isArray(obj) && obj.every(isProduct) } const isProduct = (obj: unknown): obj is Product => { return obj != null && typeof (obj as Product).id === 'string' } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Typescript の ユーザー定義型ガード
  30. With Aspida import aspida from '@aspida/axios' import api from '../api/$api'

    const client = api(aspida(fetch, { baseURL: 'https://api.mysite.com' })) const loadProducts = async(): Promise<Product[]> => { const res = await client.products.$get() // res: { products: Product[]} return res } 1 2 3 4 5 6 7 8 9 10 11 12 13 export type Methods = { get: { resBody: { products: Product[] } } } 1 2 3 4 5 6 7 api/products/index.ts
  31. With Aspida import aspida from '@aspida/axios' import api from '../api/$api'

    const client = api(aspida(fetch, { baseURL: 'https://api.mysite.com' })) const loadProducts = async(): Promise<Product[]> => { const res = await client.products.$get() // res: { products: Product[]} return res } 1 2 3 4 5 6 7 8 9 10 11 12 13 const client = api(aspida(fetch, { baseURL: 'https://api.mysite.com' })) import aspida from '@aspida/axios' 1 import api from '../api/$api' 2 3 4 5 6 const loadProducts = async(): Promise<Product[]> => { 7 const res = await client.products.$get() 8 // res: { products: Product[]} 9 10 return res 11 } 12 13 export type Methods = { get: { resBody: { products: Product[] } } } 1 2 3 4 5 6 7 api/products/index.ts
  32. With Aspida import aspida from '@aspida/axios' import api from '../api/$api'

    const client = api(aspida(fetch, { baseURL: 'https://api.mysite.com' })) const loadProducts = async(): Promise<Product[]> => { const res = await client.products.$get() // res: { products: Product[]} return res } 1 2 3 4 5 6 7 8 9 10 11 12 13 const client = api(aspida(fetch, { baseURL: 'https://api.mysite.com' })) import aspida from '@aspida/axios' 1 import api from '../api/$api' 2 3 4 5 6 const loadProducts = async(): Promise<Product[]> => { 7 const res = await client.products.$get() 8 // res: { products: Product[]} 9 10 return res 11 } 12 13 const loadProducts = async(): Promise<Product[]> => { const res = await client.products.$get() // res: { products: Product[]} return res } import api from '../api/$api' 2 3 const client = api(aspida(fetch, { baseURL: 'https://api.mysite.com' })) 4 5 6 7 8 9 10 11 12 13 14 export type Methods = { get: { resBody: { products: Product[] } } } 1 2 3 4 5 6 7 api/products/index.ts
  33. Code 補完ありがたや〜 😭

  34. 3 つめのプロジェクト 医学部インターン⽣ join ! 期限は1.5 ヶ⽉間 Web site は

    2 つ 実装 2021. 7-8
  35. 役割分担 → メイン実装、レビュー → メイン実装

  36. 技術選定 HTTP client: axios + aspida CSS frameWork: tailwind test:

    jest, Cypress, Mock Service Worker Next.js + Typescript Data fetching: useSWR new! new!
  37. Data Fetch Model Frontend Backend Render 毎回server にfetch する必要あり・・・😓 Rerender

    Fetch
  38. Cache Control max-age Client Server Cache max-age: 600

  39. < 600s > 600s Client Server Cache Client Server Cache

    適切なMax-age の設定が難しい・・・😓
  40. Cache Control max-age stale-while-revalidate Client Server max-age: 600 stale-while-revalidate: 100

  41. < 600s < 700s Client Server Cache Client Server Cache

    ① ② ①: stale ( 新鮮でない) ②: revalidate: ( 再検証) ③: cache 更新 ③
  42. useSWR fetcher 省略したらどうなる? 🤔

  43. ソースコード revalidate されなくなる

  44. Client Server Cache ① ② ③ useSWR で、cache を利⽤した 状態管理ができる

  45. import useSWR from "swr" export const useStaffNumber = (initialData: string)

    => { const { data: staffNumber, mutate: setStaffNumber } = useSWR( "staffNumber", null, { initialData } ) return [staffNumber as string, setStaffNumber] as const } 1 2 3 4 5 6 7 8 9 10 11 12 実戦例 import { useStaffNumber } from "../hooks/useSWR" const StaffNumber = () => { const [staffNumber, setStaffNumber] = useStaffNumber("") const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => setStaffNumber(event.currentTarget.value) return ( <input type="text" placeholder="7 桁の⽒名コードを⼊⼒してください" value={staffNumber} onChange={handleChange} /> ) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
  46. import useSWR from "swr" export const useStaffNumber = (initialData: string)

    => { const { data: staffNumber, mutate: setStaffNumber } = useSWR( "staffNumber", null, { initialData } ) return [staffNumber as string, setStaffNumber] as const } 1 2 3 4 5 6 7 8 9 10 11 12 "staffNumber", import useSWR from "swr" 1 2 export const useStaffNumber = (initialData: string) => { 3 const { data: staffNumber, mutate: setStaffNumber } = useSWR( 4 5 null, 6 { 7 initialData 8 } 9 ) 10 return [staffNumber as string, setStaffNumber] as const 11 } 12 実戦例 import { useStaffNumber } from "../hooks/useSWR" const StaffNumber = () => { const [staffNumber, setStaffNumber] = useStaffNumber("") const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => setStaffNumber(event.currentTarget.value) return ( <input type="text" placeholder="7 桁の⽒名コードを⼊⼒してください" value={staffNumber} onChange={handleChange} /> ) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
  47. import useSWR from "swr" export const useStaffNumber = (initialData: string)

    => { const { data: staffNumber, mutate: setStaffNumber } = useSWR( "staffNumber", null, { initialData } ) return [staffNumber as string, setStaffNumber] as const } 1 2 3 4 5 6 7 8 9 10 11 12 "staffNumber", import useSWR from "swr" 1 2 export const useStaffNumber = (initialData: string) => { 3 const { data: staffNumber, mutate: setStaffNumber } = useSWR( 4 5 null, 6 { 7 initialData 8 } 9 ) 10 return [staffNumber as string, setStaffNumber] as const 11 } 12 null, import useSWR from "swr" 1 2 export const useStaffNumber = (initialData: string) => { 3 const { data: staffNumber, mutate: setStaffNumber } = useSWR( 4 "staffNumber", 5 6 { 7 initialData 8 } 9 ) 10 return [staffNumber as string, setStaffNumber] as const 11 } 12 実戦例 import { useStaffNumber } from "../hooks/useSWR" const StaffNumber = () => { const [staffNumber, setStaffNumber] = useStaffNumber("") const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => setStaffNumber(event.currentTarget.value) return ( <input type="text" placeholder="7 桁の⽒名コードを⼊⼒してください" value={staffNumber} onChange={handleChange} /> ) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
  48. import useSWR from "swr" export const useStaffNumber = (initialData: string)

    => { const { data: staffNumber, mutate: setStaffNumber } = useSWR( "staffNumber", null, { initialData } ) return [staffNumber as string, setStaffNumber] as const } 1 2 3 4 5 6 7 8 9 10 11 12 "staffNumber", import useSWR from "swr" 1 2 export const useStaffNumber = (initialData: string) => { 3 const { data: staffNumber, mutate: setStaffNumber } = useSWR( 4 5 null, 6 { 7 initialData 8 } 9 ) 10 return [staffNumber as string, setStaffNumber] as const 11 } 12 null, import useSWR from "swr" 1 2 export const useStaffNumber = (initialData: string) => { 3 const { data: staffNumber, mutate: setStaffNumber } = useSWR( 4 "staffNumber", 5 6 { 7 initialData 8 } 9 ) 10 return [staffNumber as string, setStaffNumber] as const 11 } 12 import useSWR from "swr" export const useStaffNumber = (initialData: string) => { const { data: staffNumber, mutate: setStaffNumber } = useSWR( "staffNumber", null, { initialData } ) return [staffNumber as string, setStaffNumber] as const } 1 2 3 4 5 6 7 8 9 10 11 12 実戦例 import { useStaffNumber } from "../hooks/useSWR" const StaffNumber = () => { const [staffNumber, setStaffNumber] = useStaffNumber("") const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => setStaffNumber(event.currentTarget.value) return ( <input type="text" placeholder="7 桁の⽒名コードを⼊⼒してください" value={staffNumber} onChange={handleChange} /> ) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
  49. import useSWR from "swr" export const useStaffNumber = (initialData: string)

    => { const { data: staffNumber, mutate: setStaffNumber } = useSWR( "staffNumber", null, { initialData } ) return [staffNumber as string, setStaffNumber] as const } 1 2 3 4 5 6 7 8 9 10 11 12 "staffNumber", import useSWR from "swr" 1 2 export const useStaffNumber = (initialData: string) => { 3 const { data: staffNumber, mutate: setStaffNumber } = useSWR( 4 5 null, 6 { 7 initialData 8 } 9 ) 10 return [staffNumber as string, setStaffNumber] as const 11 } 12 null, import useSWR from "swr" 1 2 export const useStaffNumber = (initialData: string) => { 3 const { data: staffNumber, mutate: setStaffNumber } = useSWR( 4 "staffNumber", 5 6 { 7 initialData 8 } 9 ) 10 return [staffNumber as string, setStaffNumber] as const 11 } 12 import useSWR from "swr" export const useStaffNumber = (initialData: string) => { const { data: staffNumber, mutate: setStaffNumber } = useSWR( "staffNumber", null, { initialData } ) return [staffNumber as string, setStaffNumber] as const } 1 2 3 4 5 6 7 8 9 10 11 12 実戦例 import { useStaffNumber } from "../hooks/useSWR" const StaffNumber = () => { const [staffNumber, setStaffNumber] = useStaffNumber("") const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => setStaffNumber(event.currentTarget.value) return ( <input type="text" placeholder="7 桁の⽒名コードを⼊⼒してください" value={staffNumber} onChange={handleChange} /> ) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const [staffNumber, setStaffNumber] = useStaffNumber("") import { useStaffNumber } from "../hooks/useSWR" 1 2 const StaffNumber = () => { 3 4 5 const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => 6 setStaffNumber(event.currentTarget.value) 7 8 return ( 9 <input 10 type="text" 11 placeholder="7 桁の⽒名コードを⼊⼒してください" 12 value={staffNumber} 13 onChange={handleChange} 14 /> 15 ) 16 } 17
  50. import useSWR from "swr" export const useStaffNumber = (initialData: string)

    => { const { data: staffNumber, mutate: setStaffNumber } = useSWR( "staffNumber", null, { initialData } ) return [staffNumber as string, setStaffNumber] as const } 1 2 3 4 5 6 7 8 9 10 11 12 "staffNumber", import useSWR from "swr" 1 2 export const useStaffNumber = (initialData: string) => { 3 const { data: staffNumber, mutate: setStaffNumber } = useSWR( 4 5 null, 6 { 7 initialData 8 } 9 ) 10 return [staffNumber as string, setStaffNumber] as const 11 } 12 null, import useSWR from "swr" 1 2 export const useStaffNumber = (initialData: string) => { 3 const { data: staffNumber, mutate: setStaffNumber } = useSWR( 4 "staffNumber", 5 6 { 7 initialData 8 } 9 ) 10 return [staffNumber as string, setStaffNumber] as const 11 } 12 import useSWR from "swr" export const useStaffNumber = (initialData: string) => { const { data: staffNumber, mutate: setStaffNumber } = useSWR( "staffNumber", null, { initialData } ) return [staffNumber as string, setStaffNumber] as const } 1 2 3 4 5 6 7 8 9 10 11 12 実戦例 import { useStaffNumber } from "../hooks/useSWR" const StaffNumber = () => { const [staffNumber, setStaffNumber] = useStaffNumber("") const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => setStaffNumber(event.currentTarget.value) return ( <input type="text" placeholder="7 桁の⽒名コードを⼊⼒してください" value={staffNumber} onChange={handleChange} /> ) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const [staffNumber, setStaffNumber] = useStaffNumber("") import { useStaffNumber } from "../hooks/useSWR" 1 2 const StaffNumber = () => { 3 4 5 const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => 6 setStaffNumber(event.currentTarget.value) 7 8 return ( 9 <input 10 type="text" 11 placeholder="7 桁の⽒名コードを⼊⼒してください" 12 value={staffNumber} 13 onChange={handleChange} 14 /> 15 ) 16 } 17 const [staffNumber, setStaffNumber] = useStaffNumber("") const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => setStaffNumber(event.currentTarget.value) import { useStaffNumber } from "../hooks/useSWR" 1 2 const StaffNumber = () => { 3 4 5 6 7 8 return ( 9 <input 10 type="text" 11 placeholder="7 桁の⽒名コードを⼊⼒してください" 12 value={staffNumber} 13 onChange={handleChange} 14 /> 15 ) 16 } 17
  51. import useSWR from "swr" export const useStaffNumber = (initialData: string)

    => { const { data: staffNumber, mutate: setStaffNumber } = useSWR( "staffNumber", null, { initialData } ) return [staffNumber as string, setStaffNumber] as const } 1 2 3 4 5 6 7 8 9 10 11 12 "staffNumber", import useSWR from "swr" 1 2 export const useStaffNumber = (initialData: string) => { 3 const { data: staffNumber, mutate: setStaffNumber } = useSWR( 4 5 null, 6 { 7 initialData 8 } 9 ) 10 return [staffNumber as string, setStaffNumber] as const 11 } 12 null, import useSWR from "swr" 1 2 export const useStaffNumber = (initialData: string) => { 3 const { data: staffNumber, mutate: setStaffNumber } = useSWR( 4 "staffNumber", 5 6 { 7 initialData 8 } 9 ) 10 return [staffNumber as string, setStaffNumber] as const 11 } 12 import useSWR from "swr" export const useStaffNumber = (initialData: string) => { const { data: staffNumber, mutate: setStaffNumber } = useSWR( "staffNumber", null, { initialData } ) return [staffNumber as string, setStaffNumber] as const } 1 2 3 4 5 6 7 8 9 10 11 12 実戦例 import { useStaffNumber } from "../hooks/useSWR" const StaffNumber = () => { const [staffNumber, setStaffNumber] = useStaffNumber("") const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => setStaffNumber(event.currentTarget.value) return ( <input type="text" placeholder="7 桁の⽒名コードを⼊⼒してください" value={staffNumber} onChange={handleChange} /> ) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const [staffNumber, setStaffNumber] = useStaffNumber("") import { useStaffNumber } from "../hooks/useSWR" 1 2 const StaffNumber = () => { 3 4 5 const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => 6 setStaffNumber(event.currentTarget.value) 7 8 return ( 9 <input 10 type="text" 11 placeholder="7 桁の⽒名コードを⼊⼒してください" 12 value={staffNumber} 13 onChange={handleChange} 14 /> 15 ) 16 } 17 const [staffNumber, setStaffNumber] = useStaffNumber("") const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => setStaffNumber(event.currentTarget.value) import { useStaffNumber } from "../hooks/useSWR" 1 2 const StaffNumber = () => { 3 4 5 6 7 8 return ( 9 <input 10 type="text" 11 placeholder="7 桁の⽒名コードを⼊⼒してください" 12 value={staffNumber} 13 onChange={handleChange} 14 /> 15 ) 16 } 17 import { useStaffNumber } from "../hooks/useSWR" const StaffNumber = () => { const [staffNumber, setStaffNumber] = useStaffNumber("") const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => setStaffNumber(event.currentTarget.value) return ( <input type="text" placeholder="7 桁の⽒名コードを⼊⼒してください" value={staffNumber} onChange={handleChange} /> ) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
  52. useContext と⽐較した感想 参考記事: Shared Hook State with SWR SWR を

    状態管理 として活⽤しているよという話
  53. まとめ Next.js のプロジェクトを3つ開発しました。 徐々に役割分担をかえてプロジェクトを担当することで 短期間で⼤きく成⻑することができた。 技術選定が毎回変わることで、新たな知⾒を得ることが できた。 成⻑は尊いと感じました・・・!😋