Preact Island组件为何无法读取服务端Context值?
问题:Preact Context在Islands组件中无法获取服务端Provider的值
我在routes/index.tsx中编写了服务端组件代码:
import GrandChildComponent from "../components/GrandChildComponent.tsx"; import { Username } from "./context.ts"; export default function App() { return ( <Username.Provider value="Bob"> <GrandChildComponent /> </Username.Provider> ) }
./context.ts的Context定义:
import { createContext } from 'preact' export const Username = createContext('alice')
当GrandChildComponent.tsx放在components目录(服务端组件)时,不管用Consumer还是useContext都能正常显示Bob:
// Consumer写法 import { Username } from "../context.ts"; export default function GrandChildComponent(){ return ( <Username.Consumer> {username => ( <span>{username}</span> )} </Username.Consumer> ) }
// useContext写法 import { useContext } from "preact/hooks"; import { Username } from "../context.ts"; export default function GrandChildComponent(){ const username = useContext(Username) return <span>{username}</span> }
但把这个组件移到islands文件夹(客户端组件)后,显示的就变成了默认值alice,两种写法都失效了。我需要从服务端读取配置传递给客户端组件处理,请问这是为什么?
原因分析
核心问题在于Islands组件的渲染机制:
components目录下的组件是服务端渲染的,完全处于服务端Provider的上下文树中,能正常读取Context值。islands目录下的组件是客户端独立挂载的——服务端只会渲染它的占位符,客户端JS加载完成后,会在浏览器中单独把这个组件挂载到DOM里,此时它并不在服务端渲染时的Provider上下文树中,自然拿不到Provider传递的Bob,只能用Context的默认值。
解决方案
根据你的需求(服务端读取配置传给客户端组件),推荐以下几种做法:
1. 直接通过Props传递值
这是最简单直接的方式:服务端组件在渲染Islands组件时,把Context的值作为props传进去。
修改routes/index.tsx:
import GrandChildComponent from "../islands/GrandChildComponent.tsx"; // 路径改成islands import { Username } from "./context.ts"; export default function App() { const usernameValue = "Bob"; // 这里可以替换成从服务端配置读取的值 return ( <Username.Provider value={usernameValue}> <GrandChildComponent username={usernameValue} /> </Username.Provider> ) }
然后修改Islands组件:
export default function GrandChildComponent({ username }: { username: string }){ return <span>{username}</span> }
2. 将服务端配置序列化后注入客户端
如果需要在多个客户端组件中共享服务端配置,可以在服务端渲染页面时,把配置序列化成全局变量,客户端组件直接读取这个变量。
比如在routes/index.tsx中添加脚本注入:
import { Head } from "$fresh/runtime.ts"; import GrandChildComponent from "../islands/GrandChildComponent.tsx"; export default function App() { const serverConfig = { username: "Bob" }; // 从服务端配置读取 return ( <> <Head> <script> window.__SERVER_CONFIG__ = ${JSON.stringify(serverConfig)}; </script> </Head> <GrandChildComponent /> </> ) }
然后在Islands组件中读取:
export default function GrandChildComponent(){ const username = (window as any).__SERVER_CONFIG__.username; return <span>{username}</span> }
3. 客户端层面重新创建Context并注入值
如果需要在客户端组件树中共享这个值,可以在客户端根组件中创建Context,把从服务端拿到的值作为Provider的value,然后其他客户端组件从这个客户端Context读取。
比如创建islands/ClientContext.tsx:
import { createContext, useContext } from "preact"; export const ClientUsername = createContext(""); export function ClientUsernameProvider({ children, value }: { children: any, value: string }) { return <ClientUsername.Provider value={value}>{children}</ClientUsername.Provider>; } export function useClientUsername() { return useContext(ClientUsername); }
然后在routes/index.tsx中用这个客户端Provider包裹Islands组件:
import GrandChildComponent from "../islands/GrandChildComponent.tsx"; import { ClientUsernameProvider } from "../islands/ClientContext.tsx"; export default function App() { const usernameValue = "Bob"; // 服务端配置的值 return ( <ClientUsernameProvider value={usernameValue}> <GrandChildComponent /> </ClientUsernameProvider> ) }
最后在Islands组件中使用:
import { useClientUsername } from "./ClientContext.tsx"; export default function GrandChildComponent(){ const username = useClientUsername(); return <span>{username}</span> }
内容的提问来源于stack exchange,提问作者Ooker
相关产品推荐
相关产品推荐

