コンテキスト
アプリケーションが大きくなるにつれて、その仮想DOMツリーは、多くの場合、深くネストされ、さまざまなコンポーネントで構成されるようになります。ツリー内のさまざまな場所にあるコンポーネントは、共通のデータ (通常は認証、ユーザープロファイル情報、キャッシュ、ストレージなどのアプリケーション状態) にアクセスする必要がある場合があります。そのすべての情報をコンポーネントのpropsとしてツリーを介して渡すことはできますが、そうするとすべてのコンポーネントがその状態すべてを認識する必要があります。たとえそれらがツリーを介して転送するだけであってもです。
コンテキストは、コンポーネントが何も意識する必要なく、ツリーを介して値を*自動的に*渡すことを可能にする機能です。これはプロバイダー/コンシューマーのアプローチを使用して行われます。
<Provider>
は、サブツリー内でコンテキストの値を設定します。<Consumer>
は、最も近い親のプロバイダーによって設定されたコンテキストの値を取得します。
まず、1つのコンポーネントのみを使用した簡単な例を見てみましょう。この例では、「ユーザー名」のコンテキスト値を*提供*し、その値を消費します。
import { createContext } from 'preact'
const Username = createContext()
export default function App() {
return (
// provide the username value to our subtree:
<Username.Provider value="Bob">
<div>
<p>
<Username.Consumer>
{username => (
// access the current username from context:
<span>{username}</span>
)}
</Username.Consumer>
</p>
</div>
</Username.Provider>
)
}
実際の使用では、コンテキストが同じコンポーネント内で提供および消費されることはまれです。コンポーネントの状態が通常、そのための最適なソリューションです。
フックでの使用
コンテキストの <Consumer>
API はほとんどのユースケースには十分ですが、スコープにネストされた関数に依存するため、少し面倒な場合があります。関数コンポーネントは代わりに Preact の useContext()
フックを使用することを選択できます。これは、仮想DOMツリー内のコンポーネントの場所で Context
の値を返します。
これが前の例をもう一度示したもので、今回は2つのコンポーネントに分割し、useContext()
を使用してコンテキストの現在の値を取得します。
import { createContext } from 'preact'
import { useContext } from 'preact/hooks'
const Username = createContext()
export default function App() {
return (
<Username.Provider value="Bob">
<div>
<p>
<User />
</p>
</div>
</Username.Provider>
)
}
function User() {
// access the current username from context:
const username = useContext(Username) // "Bob"
return <span>{username}</span>
}
User
が複数のコンテキストの値にアクセスする必要がある場合を想像すると、より単純な useContext()
API の方がはるかに理解しやすくなります。
現実的な使用
コンテキストのより現実的な使用は、アプリケーションの認証状態(ユーザーがログインしているかどうか)を格納することです。
これを行うには、AuthContext
という名前の情報を保持するためのコンテキストを作成できます。AuthContext の値は、サインインしたユーザーを含む user
プロパティと、その状態を変更する setUser
メソッドを持つオブジェクトになります。
import { createContext } from 'preact'
import { useState, useMemo, useContext } from 'preact/hooks'
const AuthContext = createContext()
export default function App() {
const [user, setUser] = useState(null)
const auth = useMemo(() => {
return { user, setUser }
}, [user])
return (
<AuthContext.Provider value={auth}>
<div class="app">
{auth.user && <p>Welcome {auth.user.name}!</p>}
<Login />
</div>
</AuthContext.Provider>
)
}
function Login() {
const { user, setUser } = useContext(AuthContext)
if (user) return (
<div class="logged-in">
Logged in as {user.name}.
<button onClick={() => setUser(null)}>
Log Out
</button>
</div>
)
return (
<div class="logged-out">
<button onClick={() => setUser({ name: 'Bob' })}>
Log In
</button>
</div>
)
}
ネストされたコンテキスト
コンテキストには、大規模なアプリケーションで非常に役立つ隠された超能力があります。コンテキストプロバイダーは、仮想DOMサブツリー内で値を「オーバーライド」するためにネストできます。さまざまなユーザーインターフェイスの部分がURLパスに基づいて表示されるWebベースのメールアプリを想像してください。
/inbox
:受信トレイを表示/inbox/compose
:受信トレイと新しいメッセージを表示/settings
:設定を表示/settings/forwarding
:転送設定を表示
現在のパスが特定のパスセグメントと一致する場合にのみ仮想DOMツリーをレンダリングする <Route path="..">
コンポーネントを作成できます。ネストされたルートの定義を簡素化するために、一致した各ルートは、一致したパスの部分を除外するために、サブツリー内の「現在のパス」コンテキスト値をオーバーライドできます。
import { createContext } from 'preact'
import { useContext } from 'preact/hooks'
const Path = createContext(location.pathname)
function Route(props) {
const path = useContext(Path) // the current path
const isMatch = path.startsWith(props.path)
const innerPath = path.substring(props.path.length)
return isMatch && (
<Path.Provider value={innerPath}>
{props.children}
</Path.Provider>
)
}
これで、この新しい Route
コンポーネントを使用してメールアプリのインターフェイスを定義できます。Inbox
コンポーネントが、その子に対して <Route path"..">
の照合を定義するために自身のパスを知る必要がないことに注意してください。
export default function App() {
return (
<div class="app">
<Route path="/inbox">
<Inbox />
</Route>
<Route path="/settings">
<Settings />
</Route>
</div>
)
}
function Inbox() {
return (
<div class="inbox">
<div class="messages"> ... </div>
<Route path="/compose">
<Compose />
</Route>
</div>
)
}
function Settings() {
return (
<div class="settings">
<h1>Settings</h1>
<Route path="/forwarding">
<Forwarding />
</Route>
</div>
)
}
デフォルトのコンテキスト値
ネストされたコンテキストは強力な機能であり、私たちはそれに気づかずに使用することがよくあります。たとえば、この章の最初の例では、<Provider value="Bob">
を使用して、ツリー内で Username
コンテキストの値を定義しました。
ただし、これは実際には Username
コンテキストのデフォルト値をオーバーライドしていました。すべてのコンテキストにはデフォルト値があり、これは createContext()
への最初の引数として渡された値です。この例では、createContext
に引数を渡さなかったので、デフォルト値は undefined
でした。
プロバイダーの代わりにデフォルトのコンテキスト値を使用した場合の最初の例は次のようになります。
import { createContext } from 'preact'
import { useContext } from 'preact/hooks'
const Username = createContext('Bob')
export default function App() {
const username = useContext(Username) // returns "Bob"
return <span>{username}</span>
}
試してみましょう!
練習として、前の章で作成したカウンターの*同期*バージョンを作成しましょう。これを行うには、この章の認証例の useMemo()
テクニックを使用する必要があります。または、count
値を共有するコンテキストと、値を更新する increment
関数を共有する別のコンテキストの*2つ*を定義することもできます。