前言
Solid.js,一个比 React 更 react 的框架。每一个使用 React 的同学,你可以不使用,但不应该不了解。
目前 Solid.js 发布了最新的官方文档,但却缺少对应的中文文档。为了帮助大家学习 Solid.js,为爱发电翻译文档。
我同时搭建了 Solid.js 最新的中文文档站点:solid.yayujs.com ,欢迎勘误。
虽说是翻译,但个人并不喜欢严格遵守原文,为了保证中文阅读流畅,会删减部分语句,对难懂的部分也会另做补充解释,希望能给大家带来一个好的中文学习体验。
欢迎围观朋友圈、加入低调务实优秀中国好青年前端社群,一个人走得快,一群人走得远。
Context
Context(上下文)提供了一种通过组件树传递数据的方法,而无需每级手动向下传递 props。
何时使用上下文
当您有一个需要共享状态的大型组件树时,可以使用上下文。使用上下文可以避免 prop drilling,实现了 props 跨层级传递而无须一层一层传递。
如果您想避免通过少数几层传递某些 props,那么在合适的情况下,调整组件层次结构可能是一个更简单的解决方案。信号通常是最简单的解决方案,因为它们可以直接导入到需要的组件中。
上下文旨在共享应用程序全局的数据或应用程序组件树中的多个组件需要经常访问的信息。上下文提供了一种跨应用程序访问状态的方法,无需通过中间层传递 props 或将它们直接导入到组件中。
创建上下文
使用 createContext 函数创建上下文。此函数有一个 Provider
属性,用于包裹您要为其提供上下文的组件树。
jsx
// /context/create.js
import { createContext } from "solid-js";
const MyContext = createContext();
// /context/component.jsx
import { MyContext } from "./create";
export function Provider (props) {
return (
<MyContext.Provider>
{props.children}
</MyContext.Provider>
)
};
为子级提供上下文
要将值传递给 Provider
,您可以使用 value
属性,它可以接收任何值,包括 signal 。一旦将值传递给 Provider
,它就可供 Provider
后代的所有组件使用。
当传递单个值时,可以直接传递给 value
属性:
jsx
// /context/component.jsx
import { createContext, useContext } from "solid-js";
import { MyContext } from "./create";
const Provider = (props) => (
<MyContext.Provider value="new value">{props.children}</MyContext.Provider>
);
说明:复杂类型
当传递多个值(比如 array
或 object
)时,建议使用 store
消费上下文
一旦这些值可供上下文组件树中的所有组件使用,就可以使用 useContext 访问它们。该方法接受上下文对象并返回传递给 Provider
的值:
jsx
// /context/component.jsx
import { createContext, useContext } from "solid-js";
import { MyContext } from "./create";
const Provider = (props) => (
<MyContext.Provider value="new value">
{props.children}
</MyContext.Provider>
);
const Child = () => {
const value = useContext(MyContext);
return (
<span>
{value}
</span>
);
};
export const App = () => (
<Provider>
<Child />
</Provider>
);
自定义上下文工具函数
当应用程序包含多个上下文对象时,可能很难跟踪正在使用哪个上下文对象。要解决此问题,您可以创建自定义工具函数来创建更易读的方式访问上下文值。
例如,当您想要创建一个可用于包裹组件树的自定义 Provider
组件,你可以让其方便复用:
jsx
import { createSignal, createContext, useContext } from "solid-js";
import { CounterContext } from "~/context/counter";
export function CounterProvider(props) {
let count = 0;
return (
<CounterContext.Provider value={count}>
{props.children}
</CounterContext.Provider>
);
}
现在,如果您必须在应用程序的不同区域访问 Provider
,您可以简单地导入 CounterProvider
组件并包裹组件树:
jsx
import { CounterProvider } from "./counterProvider";
export function App() {
return (
<CounterProvider count={1}>
<h1>Welcome to Counter</h1>
<NestedComponents />
</CounterProvider>
);
}
同样,您可以创建自定义工具函数来访问上下文值。创建自定义工具函数可以更轻松地访问您需要的值,而不是导入 useContext
并在您使用它的每个组件上传递上下文对象:
jsx
export function useCounter() {
return useContext(CounterContext);
}
这个例子中的 useCounter()
工具函数现在可以导入到需要访问上下文值的任何组件中:
jsx
import { useCounter } from "./counter";
export function CounterProvider(props) {
const count = useCounter();
return (
<>
<div>{count()}</div>
</>
);
}
更新上下文值
信号提供了一种使用上下文同步和管理跨组件共享数据的方法。您可以将信号直接传递到 Provider
组件的 value
属性,对信号的任何更改都将反映在使用上下文的所有组件中。
jsx
// App.jsx
import { CounterProvider } from "./Context";
import { Child } from "./Child";
export function App() {
return (
<CounterProvider count={1}>
<h1>Welcome to Counter App</h1>
<Child />
</CounterProvider>
)
}
// Context.jsx
import { createSignal, useContext } from "solid-js";
export function CounterProvider(props) {
const [count, setCount] = createSignal(props.initialCount || 0);
const counter = [
count,
{
increment() {
setCount(prev => prev + 1);
},
decrement() {
setCount(prev => prev - 1);
}
}
];
return (
<CounterContext.Provider value={counter}>
{props.children}
</CounterContext.Provider>
);
}
export function useCounter() { return useContext(CounterContext); }
// Child.jsx
import { useCounter } from "./Context";
export function Child(props) {
const [count, { increment, decrement }] = useCounter();
return (
<>
<div>{count()}</div>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</>
);
};
这提供了一种管理组件状态的方法,而无须通过中间元素传递 props。
上下文调试
createContext
接收一个可选的默认值,如果未提供,它可能会返回 undefined
。使用 TypeScript 时,这可能会导致类型问题,从而很难确定组件未按预期渲染的原因。
要解决此问题,可以在创建上下文对象时指定默认值,或者通过使用自定义 useMyContext
手动处理错误:
jsx
// /context/counter-component.tsx
import { useContext } from "solid-js";
function useMyContext() {
const value = useContext(MyContext);
if (!value) {
throw new Error("Missing context Provider");
}
return value;
}
function Child() {
const value = useMyContext();
return <div>{value}</div>;
}
createContext 和 useContext 的常见问题
如果没有将默认值传递给 createContext
,则 useContext
可能会返回 undefined
。
因此,如果未将初始值传递给 createContext
,则 useContext
的 TS 类型签名将指示返回的值可能是 undefined
(如上所述)。当您想要在组件内使用上下文时,尤其是立即解构上下文时,这可能会非常烦人。此外,如果您使用 useContext
并且它返回 undefined
(这通常但并非总是错误的结果),运行时抛出的错误消息可能会令人困惑。
最常见的解决方案是将 useContext
的所有使用包裹在一个函数中,如果上下文是 undefined
,该函数将显式抛出有用的错误。这也可以收窄返回的类型,因此 TS 不会提示错误。举个例子:
jsx
// /context/counter-component.tsx
function useCounterContext() {
const context = useContext(CounterContext)
if (!context) {
throw new Error("can't find CounterContext")
}
return context
}
系列文章
本篇已收录在掘金专栏 《Solid.js 中文文档》。
此外我还写过 React 系列、TypeScript 系列、Next.js 系列、VuePress 博客搭建系列、JavaScript 系列等多个系列文章,全系列文章目录:github.com/mqyqingfeng...
通过文字建立交流本身就是一种缘分,欢迎围观朋友圈、加入低调务实优秀中国好青年前端社群,一个人走得快,一群人走得远。