Last active
March 11, 2020 06:20
-
-
Save kazuma1989/0714d3968b9f5a86f59896d153663605 to your computer and use it in GitHub Desktop.
MobX と hooks でプレーンな書き味の React コンポーネントを書く (https://qiita.com/kazuma1989/items/16f68cf835031b03fb61)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useEffect } from 'react' | |
import { Section, Title, Loading, Todo } from './components' | |
import useTodosStore from './useTodosStore' | |
export default function App() { | |
// このファイルにべた書きされた store インスタンスではなく、コンテキスト経由の store を使うことで、 | |
// テスト時や Storybook を使うときにモックしやすくなる。 | |
const [todos, loading, toggle, fetchTodos] = useTodosStore(store => [ | |
store.todos, | |
store.loading, | |
store.toggle, | |
store.fetch, | |
]) | |
useEffect(() => { | |
fetchTodos() | |
}, [fetchTodos]) | |
if (loading) { | |
return ( | |
<Section> | |
<Loading /> | |
</Section> | |
) | |
} | |
return ( | |
<Section> | |
<Title>Todos</Title> | |
{todos?.map(({ id, title, completed }) => ( | |
<Todo | |
key={id} | |
label={title} | |
completed={completed} | |
onChange={() => toggle(id)} | |
/> | |
))} | |
</Section> | |
) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { observable, computed, action, flow } from 'mobx' | |
type Todo = { | |
userId: number | |
id: number | |
title: string | |
completed: boolean | |
} | |
/** | |
* TODO 一覧を管理する store | |
*/ | |
export default class TodosStore { | |
/** | |
* TODO 一覧 | |
*/ | |
@observable todos?: Todo[] | |
/** | |
* TODO 一覧を読み込み中のとき true | |
*/ | |
@computed get loading() { | |
return !this.todos | |
} | |
/** | |
* TODO の一つの完了/未完了を切り替える | |
* | |
* @param id 対象の TODO の ID | |
*/ | |
@action.bound toggle(id: number) { | |
this.todos = this.todos?.map(todo => { | |
if (todo.id === id) { | |
todo.completed = !todo.completed | |
} | |
return todo | |
}) | |
} | |
/** | |
* TODO 一覧を API から取得する | |
*/ | |
fetch = flow(function*(this: TodosStore) { | |
this.todos = undefined | |
this.todos = yield fetch( | |
'https://jsonplaceholder.typicode.com/todos?userId=1', | |
).then(r => r.json()) | |
}).bind(this) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useContext } from 'react' | |
import { useObserver } from 'mobx-react' | |
export type Selector<TStore, TSelection> = (store: TStore) => TSelection | |
// useContext と useObserver を組み合わせた、任意の store 型に対応したカスタムフック。 | |
// この hook を介して store slice を取得すれば、コンポーネントが store の mutable な変更を検知できる。 | |
export default function useStore<TStore, TSelection>( | |
context: React.Context<TStore>, | |
selector: Selector<TStore, TSelection>, | |
) { | |
const store = useContext(context) | |
if (!store) { | |
throw new Error('need to pass a value to the context') | |
} | |
return useObserver(() => selector(store)) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createContext } from 'react' | |
import useStore, { Selector } from './useStore' | |
import TodosStore from './TodosStore' | |
const context = createContext<TodosStore | null>(null) | |
export const TodosProvider = context.Provider | |
// TodosStore の slice を取得するための hook | |
// 汎用の useStore を TodosStore 専用にした。 | |
export default function useTodosStore<TSelection>(selector: Selector<TodosStore, TSelection>) { | |
return useStore(context, selector) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment