Slide 1

Slide 1 text

2021/09/15 @0906koki SWR ͱঢ়ଶ؅ཧ Redux Ͱ͸ͳ͘ɺSWRͱ͍͏બ୒

Slide 2

Slide 2 text

Koki Nagai @0906koki ɾגࣜձࣾελϝϯ ɾFANTSͱ͍͏ΦϯϥΠϯϑΝϯαϩϯͷαʔϏεΛ࡞͍ͬͯ·͢ ɾϑϩϯτΤϯυΤϯδχΞʢओʹ ReactͱTypeScriptɺNext.jsʣ ɾझຯ͸ےτϨ

Slide 3

Slide 3 text

SWR ͱঢ়ଶ؅ཧ

Slide 4

Slide 4 text

SWR ͱ͸ʁ

Slide 5

Slide 5 text

👀

Slide 6

Slide 6 text

SWR ͱ͸ʁ • React ͷσʔλϑΣονϥΠϒϥϦ • SWR ͱ͍͏໊લ͸ɺHTTP RFC 5861 ͷ stale-while-revalidate͔ Βདྷ͍ͯΔ • Next.js ͷ։ൃݩͷ Vercel ͕։ൃ • ฐࣾελϝϯͷ৽نϓϩδΣΫτͰ࠾༻

Slide 7

Slide 7 text

αϯϓϧίʔυ 💻

Slide 8

Slide 8 text

import useSWR from "swr"; const fetcher = async () => { const res = await fetch(`/api/ v1/user`); return res.json(); }; export const User = () => { const { data, error } = useSWR(`/api/user`, fetcher); if (!data) return

loading...

; if (!!error) return

😈

; return

{data.name}

; };

Slide 9

Slide 9 text

import useSWR from "swr"; const fetcher = async () => { const res = await fetch(`/api/ v1/user`); return res.json(); }; export const User = () => { const { data, error } = useSWR(`/api/user`, fetcher); if (!data) return

loading...

; if (!!error) return

😈

; return

{data.name}

; }; • ୈҰҾ਺ʹstringͷkey • ୈೋҾ਺ʹPromiseΛฦؔ͢਺Λࢦఆ • Fetch API Ͱ΋ AxiosͰ΋࢖༻Մೳ

Slide 10

Slide 10 text

SWR ͷಛ௃

Slide 11

Slide 11 text

SWR ͷಛ௃ • ୈҰҾ਺ͷ Key ʹରԠͯ͠ɺϑΣονͨ͠σʔλΛΩϟογϡ͢Δ • SWR ͷ಺෦ͰɺMap ΦϒδΣΫτͱͯ͠ϝϞϦΩϟογϡ • σʔλͷࣗಈ࠶ݕূ • Hooks ϑΝʔετ • ύοέʔδͷαΠζ͕3.9KBʂʂʢ react-query ͕12.3KB ʣ

Slide 12

Slide 12 text

σʔλͷࣗಈ࠶ݕূ import useSWR from "swr"; const fetcher = async () => { const res = await fetch(`/api/v1/ user`); return res.json(); }; export const User = () => { const { data, error } = useSWR(`/ api/user`, fetcher); if (!data) return

loading...

; if (!!error) return

😈

; return

{data.name}

; };

Slide 13

Slide 13 text

SWR ͷࣗಈ࠶ݕূ • User ίϯϙʔωϯτ͕ΞϯϚ΢ϯτ͞Εͯ΋ɺϑΣονͨ͠σʔλ͸ Ωϟογϡͱͯ͠อଘ • ࠶౓ User ίϯϙʔωϯτΛϚ΢ϯτͯ͠΋ɺॳճϨϯμʔ࣌͸Ωϟο γϡͨ͠σʔλΛ࢖༻ • ͔͠͠ɺόοΫάϥ΢ϯυͰσʔλΛ࠶ݕূͯ͠σʔλΛߋ৽ • ΢Οϯυ΢ͷϑΥʔΧε࣌΍ΦϑϥΠϯ࣌ͳͲɺࡉ͔͘ઃఆ͕Մೳ

Slide 14

Slide 14 text

👍

Slide 15

Slide 15 text

ͳͥ SWR Λ࠾༻ͨ͠ͷ͔ʁ

Slide 16

Slide 16 text

ͳͥ SWR Λ࠾༻ͨ͠ͷ͔ʁ • ฐࣾελϝϯͰ͸ঢ়ଶ؅ཧͱͯ͠ Redux Λ࠾༻͍ͯͨ͠ • ͨͩɺRedux ͸ϑΣονʹؔΘΔ࣮૷͕ް͘ɺϘΠϥʔϓϨʔτʹΑͬͯ ίʔυྔ͕ଟ͘ͳΔ • Ճ͑ͯɺࠓճͷΞϓϦέʔγϣϯಛੑ্ɺঢ়ଶͱͯ࣋ͭ͠ର৅͕αʔόʔσ ʔλ͕΄ͱΜͲͰɺΫϥΠΞϯτͱͯ࣋ͭ͠άϩʔόϧͳঢ়ଶ͸ݶΒΕ͍ͯ ͨʢࠓճάϩʔόϧͳΫϥΠΞϯτঢ়ଶ؅ཧ͸ useContext Λ࢖༻ʣ

Slide 17

Slide 17 text

react-query ։ൃऀͷ Tweet

Slide 18

Slide 18 text

Redux Λ࢖Θͳͯ͘΋͍͍ͷͰ͸ʁ

Slide 19

Slide 19 text

Redux ͰϑΣονपΓΛ࣮૷͢Δͱ…

Slide 20

Slide 20 text

Reducer const currentUserSlice = createSlice({ name: 'user', initialState, reducers: { requestFetchUser: (state) => { return { ...state, isLoading: true } }, successFetchUser: (state, { payload }: PayloadAction) => { return { ...state, user: payload, isLoading: false, } }, failureFetchUser: (state, { payload }: PayloadAction) => { return { ...state, error: payload, isLoading: false, } }, } })

Slide 21

Slide 21 text

Middleware ͱ fecherʢRedux-Saga ͷ৔߹ʣ function* runRequestFetchUser() { const { payload, error }: ResponseType = yield call(requestFetchUser) if (payload) { yield put(successFetchUser) } else if (!!error) { yield put(failureFetchUser) } } function* handleRequestFetchUser() { yield takeEvery(requestFetchUser.type, runRequestFetchUser) } const requestFetchUser = async () => { const res = await fetch(`/user`) return res.json() }

Slide 22

Slide 22 text

SWR ͷ৔߹

Slide 23

Slide 23 text

import useSWR from "swr"; const fetcher = async () => { const res = await fetch(`/api/v1/user`); return res.json(); }; export const User = () => { const { data, error } = useSWR(`/api/user`, fetcher); // ... };

Slide 24

Slide 24 text

import useSWR from "swr"; const fetcher = async () => { const res = await fetch(`/api/v1/user`); return res.json(); }; export const User = () => { const { data, error } = useSWR(`/api/user`, fetcher); // ... }; So Simpleʂ🎉

Slide 25

Slide 25 text

SWR Λ࢖࣮ͬͨ૷ྫ github ʹίʔυΛ্͍͛ͯ·͢

Slide 26

Slide 26 text

TODO Λฤू͢ΔέʔεΛߟ͑Δ

Slide 27

Slide 27 text

SWR Λ࢖࣮ͬͨ૷ྫ ~ ฤूϑΥʔϜ ✅ ฤूϑΥʔϜͰ͸ҎԼͷॲཧ͕ඞཁ 1. ฤूର৅ͷσʔλΛϑΣον͢Δॲཧ 2. ϑΥʔϜͷঢ়ଶΛ؅ཧ͢Δॲཧ 3. ฤूͨ͠σʔλΛ PATCH ͢Δॲཧ

Slide 28

Slide 28 text

SWR Λ࢖࣮ͬͨ૷ྫ ~ ฤूϑΥʔϜ ✅ ฤूϑΥʔϜͰ͸ҎԼͷॲཧ͕ඞཁ 1. ฤूର৅ͷσʔλΛϑΣον͢Δॲཧ 2. ϑΥʔϜͷঢ়ଶΛ؅ཧ͢Δॲཧ 3. ฤूͨ͠σʔλΛ PATCH ͢Δॲཧ

Slide 29

Slide 29 text

SWR Λ࢖࣮ͬͨ૷ྫ ~ ฤूϑΥʔϜ લ४උ: useSWR Λϥοϓ͢Δؔ਺Λఆٛ export const useFetch = ({ key, fetcher }: ArgsType): IResponse => { const { data, error, isValidating, mutate } = useSWR(key, fetcher); return { data, error, isValidating, mutate, } as const; };

Slide 30

Slide 30 text

1. ฤूର৅ͷσʔλΛϑΣον 2. ϑΥʔϜͷঢ়ଶ؅ཧ͢Δॲཧ const initialData: ResponseTodoType = { id: 0, userId: 0, title: "", completed: false, }; interface IResponse { data: ResponseTodoType | undefined; onChangeTitle: (value: string) => void; } export const useFetchTodo = (): IResponse => { const { data, mutate } = useFetch({ key: "/todos/1", fetcher: () => requestFetchTodo(1), }); const handleOnChangeTitle = useCallback((value: string) => { mutate((data = initialData) => { return { ...data, title: value }; }, false); }, []); return { data, onChangeTitle: handleOnChangeTitle, } as const; };

Slide 31

Slide 31 text

• ઌఔఆٛͨ͠ useFetch Λݺͼग़͢ • ϑΣονͨ͠σʔλΛ SWR ͷ಺෦ ͰΩϟογϡ͢Δ const initialData: ResponseTodoType = { id: 0, userId: 0, title: "", completed: false, }; interface IResponse { data: ResponseTodoType | undefined; onChangeTitle: (value: string) => void; } export const useFetchTodo = (): IResponse => { const { data, mutate } = useFetch({ key: "/todos/1", fetcher: () => requestFetchTodo(1), }); const handleOnChangeTitle = useCallback((value: string) => { mutate((data = initialData) => { return { ...data, title: value }; }, false); }, []); return { data, onChangeTitle: handleOnChangeTitle, } as const; }; 1. ฤूର৅ͷσʔλϑΣονॲཧ

Slide 32

Slide 32 text

• titleͷঢ়ଶΛߋ৽͢Δؔ਺ • mutateͷୈҰҾ਺ͷcallbackΛ౉ ͢͜ͱͰɺ࠷৽ͷΩϟογϡΛऔಘ • ୈೋҾ਺Ͱ false Λࢦఆ͢Δ͜ͱͰɺ σʔλ࠶ݕূͷϦΫΤετΛ๷ࢭ const initialData: ResponseTodoType = { id: 0, userId: 0, title: "", completed: false, }; interface IResponse { data: ResponseTodoType | undefined; onChangeTitle: (value: string) => void; } export const useFetchTodo = (): IResponse => { const { data, mutate } = useFetch({ key: "/todos/1", fetcher: () => requestFetchTodo(1), }); const handleOnChangeTitle = useCallback((value: string) => { mutate((data = initialData) => { return { ...data, title: value }; }, false); }, []); return { data, onChangeTitle: handleOnChangeTitle, } as const; }; 2. ϑΥʔϜͷঢ়ଶ؅ཧ͢Δॲཧ

Slide 33

Slide 33 text

SWR Λ࢖࣮ͬͨ૷ྫ ~ ฤूϑΥʔϜ ฤूϑΥʔϜͰ͸ҎԼͷॲཧ͕ඞཁ 1. ฤूର৅ͷσʔλΛϑΣον͢Δॲཧ 2. ϑΥʔϜͷঢ়ଶΛ؅ཧ͢Δॲཧ 3. ฤूͨ͠σʔλΛ PATCH ͢Δॲཧ

Slide 34

Slide 34 text

3. ฤूͨ͠σʔλΛ PATCH ͢Δॲཧ import { mutate } from "swr"; type RequestType = Pick; export const useRequestPatchTodo = () => { const [isRequesting, setIsRequesting] = useState(false); const [requestTargets, setRequestTargets] = useState({ id: 0, title: "", }); const handleOnRequest = async () => { const res = await requestPatchTodo(requestTargets); if (res) { alert("success!"); mutate("/todos/1"); } else { console.log("failure"); } }; const handleOnSubmit = (args: RequestType) => { setRequestTargets(args); setIsRequesting(true); }; useEffect(() => { if (isRequesting) { handleOnRequest(); } }, [isRequesting]); return { onRequestPatchTodo: handleOnSubmit, }; };

Slide 35

Slide 35 text

3. ฤूͨ͠σʔλΛ PATCH ͢Δॲཧ import { mutate } from "swr"; type RequestType = Pick; export const useRequestPatchTodo = () => { const [isRequesting, setIsRequesting] = useState(false); const [requestTargets, setRequestTargets] = useState({ id: 0, title: "", }); const handleOnRequest = async () => { const res = await requestPatchTodo(requestTargets); if (res) { alert("success!"); mutate("/todos/1"); } else { console.log("failure"); } }; const handleOnSubmit = (args: RequestType) => { setRequestTargets(args); setIsRequesting(true); }; useEffect(() => { if (isRequesting) { handleOnRequest(); } }, [isRequesting]); return { onRequestPatchTodo: handleOnSubmit, }; }; • requestPatchTodoͰ࣮ࡍʹAPI ϦΫΤετΛૹ৴ • ̼utateΛ࣮ߦͯ͠ɺϑΣον͢Δ ͜ͱͰɺαʔόʔͱͷঢ়ଶΛಉظ͢ Δ

Slide 36

Slide 36 text

🎃 ൪֎ฤ

Slide 37

Slide 37 text

৽نͷTODOΛPOST͢Δॲཧ export const usePostTodo = () => { const { data, mutate } = useFetch<{ title: string }>({ key: "/todos/new", fetcher: null, }); const [isRequesting, setIsRequesting] = useState(false); const handleOnChangeTitle = useCallback((value: string) => { mutate((data = initialData) => { return { ...data, title: value }; }); }, []); const handleOnRequest = async () => { const res = await requestPostTodo(title); if (res) { alert("success!"); } else { console.log("failure"); } }; useEffect(() => { if (isRequesting) { handleOnRequest(); } }, [isRequesting]); return { data, onChangeTitle: handleOnChangeTitle, onRequestPost: useCallback(() => setIsRequesting(true), []) }; };

Slide 38

Slide 38 text

৽نͷTODOΛPOST͢Δॲཧ export const usePostTodo = () => { const { data, mutate } = useFetch<{ title: string }>({ key: "/todos/new", fetcher: null, }); const [isRequesting, setIsRequesting] = useState(false); const handleOnChangeTitle = useCallback((value: string) => { mutate((data = initialData) => { return { ...data, title: value }; }); }, []); const handleOnRequest = async () => { const res = await requestPostTodo(title); if (res) { alert("success!"); } else { console.log("failure"); } }; useEffect(() => { if (isRequesting) { handleOnRequest(); } }, [isRequesting]); return { data, onChangeTitle: handleOnChangeTitle, onRequestPost: useCallback(() => setIsRequesting(true), []) }; }; • ຊདྷ Promise ͷؔ਺Λ౉͍ͯͨ͠ ͱ͜Ζʹ null Λ౉͢͜ͱͰɺϑΣο νॲཧΛڬΉ͜ͱͳ͘ɺ७ਮͳঢ়ଶ ؅ཧͱͯ͠࢖༻͕Մೳ

Slide 39

Slide 39 text

·ͱΊ

Slide 40

Slide 40 text

• SWR ͱ͸ ReactͷϑΣονϥΠϒϥϦ • ฐࣾͷϓϩδΣΫτͰ͸ɺαʔόʔͷঢ়ଶ؅ཧͱͯ͠ ReduxͰ ͸ͳ͘ɺSWR Λ࠾༻ • SWR Λ࢖༻͢Δ͜ͱͰɺϑΣον΍Ωϟογϡॲཧ͕γϯϓϧ ʹ࣮૷͕Ͱ͖Δ

Slide 41

Slide 41 text

MeetyͰ໘ஊΛืू͍ͯ͠ΔͷͰɺฐࣾͷϑϩϯτΤϯυʹڵຯ ͋Δํ͸࿩͠·͠ΐ͏ʂ🤗 ͍͞͝ʹ