Skip to content

Docs[Translate] Update and Revise Chinese Guide Translations #1277

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 18, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 134 additions & 24 deletions content/zh/guide/v10/context.md
Original file line number Diff line number Diff line change
@@ -1,74 +1,184 @@
---
title: 上下文
description: '上下文可让您通过中间组件传递属性。此文档同时提供新版本和老版本 API 的使用方法。'
description: '上下文允许您通过中间组件传递属性。本文档描述了新旧两种API'
---

# 上下文

上下文允许你直接传值到组件树下方,无需使用 prop 通过中间组件传递。主题是一个最常用的的用例。简而言之,可以将上下文看作 Preact 中发布订阅模式的更新方式
上下文是一种在组件树中传递数据的方式,无需通过props将其传递给中间的每个组件。简而言之,它允许层次结构中的任何位置的组件订阅一个值并在其变化时收到通知,为Preact带来发布-订阅风格的更新

使用上下文有两种方式:使用新版的 `createContext` API,或是使用旧版上下文 API。两者区别在于旧版 API 无法在中间组件 `shouldComponentUpdate` 终止渲染时更新子组件。这也是我们强烈建议您使用 `createContext` 的原因。
在某些情况下,需要将祖父组件(或更高层级)的值传递给子组件,而中间组件往往不需要这个值,这种情况并不少见。这种传递props的过程通常被称为"prop钻取"(prop drilling),它可能会变得繁琐、容易出错,而且非常重复,尤其是随着应用程序的增长,更多的值必须通过更多的层级传递。这是上下文旨在解决的关键问题之一,它提供了一种方式让子组件订阅组件树中更高层级的值,无需通过prop传递就能访问该值。

在Preact中使用上下文有两种方式:通过较新的`createContext` API和传统上下文API。如今,几乎没有理由使用传统API,但为了完整性这里也会进行记录。

---

<toc></toc>

---

## createContext
## 现代上下文API

首先,我们需要通过 `createContext(initialValue)` 函数创建一个可以传递的上下文对象。此函数返回一个用于设置上下文中值的 `Provider` 组件和用于从上下文中取回值的 `Consumer` 组件。
### 创建上下文

`initialValue` 参数仅在组件树中不存在对应的 `Provider` 组件时使用,这有助于独立地测试组件,它避免了封装 `Provider`
要创建新的上下文,我们使用`createContext`函数。此函数接受一个初始状态作为参数,并返回一个具有两个组件属性的对象:`Provider`,使上下文对后代可用;以及`Consumer`,用于访问上下文值(主要在类组件中)。

```jsx
import { createContext } from "preact";

export const Theme = createContext("light");
export const User = createContext({ name: "Guest" });
export const Locale = createContext(null);
```

### 设置Provider

创建上下文后,我们必须使用`Provider`组件使其对后代可用。`Provider`必须提供一个`value`属性,表示上下文的初始值。

> 只有在树中消费者上方没有`Provider`的情况下,才会使用从`createContext`设置的初始值。这对于单独测试组件可能很有帮助,因为它避免了在组件周围创建包装`Provider`的需要。
```jsx
import { createContext } from "preact";

export const Theme = createContext("light");

function App() {
return (
<Theme.Provider value="dark">
<SomeComponent />
</Theme.Provider>
);
}
```

> **提示:** 您可以在整个应用程序中拥有同一上下文的多个provider,但只会使用离消费者最近的那个。
### 使用上下文

消费上下文有两种方式,主要取决于您喜欢的组件风格:`Consumer`(类组件)和`useContext`钩子(函数组件/钩子)。

<tab-group tabstring="Consumer, useContext">

```jsx
// --repl
import { render, createContext } from 'preact';
import { render, createContext } from "preact";

const SomeComponent = props => props.children;
// --repl-before
const Theme = createContext('light');
const ThemePrimary = createContext("#673ab8");

function ThemedButton(props) {
function ThemedButton() {
return (
<Theme.Consumer>
{theme => {
return <button {...props} class={'btn ' + theme}>主题按钮</button>;
}}
</Theme.Consumer>
<ThemePrimary.Consumer>
{theme => <button style={{ background: theme }}>主题按钮</button>}
</ThemePrimary.Consumer>
);
}

function App() {
return (
<Theme.Provider value="dark">
<ThemePrimary.Provider value="#8f61e1">
<SomeComponent>
<ThemedButton />
</SomeComponent>
</Theme.Provider>
</ThemePrimary.Provider>
);
}
// --repl-after
render(<App />, document.getElementById("app"));
```

```jsx
// --repl
import { render, createContext } from "preact";
import { useContext } from "preact/hooks";

const SomeComponent = props => props.children;
// --repl-before
const ThemePrimary = createContext("#673ab8");

function ThemedButton() {
const theme = useContext(ThemePrimary);
return <button style={{ background: theme }}>主题按钮</button>;
}

function App() {
return (
<ThemePrimary.Provider value="#8f61e1">
<SomeComponent>
<ThemedButton />
</SomeComponent>
</ThemePrimary.Provider>
);
}
// --repl-after
render(<App />, document.getElementById("app"));
```

</tab-group>

### 更新上下文

静态值可能有用,但更多时候,我们希望能够动态更新上下文值。为此,我们利用标准组件状态机制:

```jsx
// --repl
import { render, createContext } from "preact";
import { useContext, useState } from "preact/hooks";

const SomeComponent = props => props.children;
// --repl-before
const ThemePrimary = createContext(null);

function ThemedButton() {
const { theme } = useContext(ThemePrimary);
return <button style={{ background: theme }}>主题按钮</button>;
}

function ThemePicker() {
const { theme, setTheme } = useContext(ThemePrimary);
return (
<input
type="color"
value={theme}
onChange={e => setTheme(e.currentTarget.value)}
/>
);
}

function App() {
const [theme, setTheme] = useState("#673ab8");
return (
<ThemePrimary.Provider value={{ theme, setTheme }}>
<SomeComponent>
<ThemedButton />
{" - "}
<ThemePicker />
</SomeComponent>
</ThemePrimary.Provider>
);
}
// --repl-after
render(<App />, document.getElementById("app"));
```

> 另一种使用 Context 的简单方法是使用 [useContext](/guide/v10/hooks#usecontext) 钩子。
## 传统上下文API

## 旧版上下文 API
此API被视为传统API,应在新代码中避免使用,它有已知问题,存在仅出于向后兼容性原因。

处于向后兼容我们仍提供此旧版 API,它已经被 `createContext` API 取代。此 API 在中间组件的 `shouldComponentUpdate` 方法返回 `false` 时会出现阻塞更新问题。若您仍需要此方法,请继续阅读
此API与新API之间的一个关键区别是,当子组件和提供者之间的组件通过`shouldComponentUpdate`中止渲染时,此API无法更新子组件。当这种情况发生时,子组件**将不会**接收到更新的上下文值,这通常会导致撕裂(部分UI使用新值,部分使用旧值)

组件需要实现 `getChildContext` 才能通过上下文传递自定义变量,你可以在此方法内返回想在上下文中存储的值。然后可以通过函数组件的第二个参数或类组件的 `this.context` 获取存储的值
要通过上下文传递值,组件需要具有`getChildContext`方法,返回预期的上下文值。然后,后代可以通过函数组件中的第二个参数或类组件中的`this.context`访问上下文

```jsx
// --repl
import { render } from 'preact';
import { render } from "preact";

const SomeOtherComponent = props => props.children;
// --repl-before
function ThemedButton(props, context) {
function ThemedButton(_props, context) {
return (
<button {...props} class={'btn ' + context.theme}>
<button style={{ background: context.theme }}>
主题按钮
</button>
);
Expand All @@ -77,7 +187,7 @@ function ThemedButton(props, context) {
class App extends Component {
getChildContext() {
return {
theme: 'light'
theme: "#673ab8"
}
}

Expand Down
4 changes: 4 additions & 0 deletions content/zh/guide/v10/debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ render(Foo, dom);

HTML 对表格的结构有着严格规则,违反其中一条都会导致渲染错误,且很难调试。在 Preact 中,我们会检测此问题并输出错误。要了解表格结构,我们强烈推荐您参阅 [MDN 文档](https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Tables/Basics)

> **注意:** 在这个语境中,“严格模式” 指的是 HTML 解析器的 输出,而非 输入。浏览器通常非常宽容,会尽可能尝试修正无效的 HTML 代码,以确保页面仍能正常显示。然而,对于 Preact 这类虚拟 DOM 库而言,这可能会引发问题。因为一旦浏览器修正了 HTML 代码,输入内容与输出内容可能会不一致,而 Preact 对此并不知情。
>
> 例如,根据规范,`<tr>` 元素必须始终作为 `<tbody>`、`<thead>` 或 `<tfoot>` 元素的子元素存在。但如果你直接将 `<tr>` 写在 `<table>` 内部,浏览器会尝试自动修正这一问题,为其添加一个 `<tbody>` 包装。此时,Preact 预期的 DOM 结构是 `<table><tr></tr></table>`,但浏览器实际构建的 DOM 结构却是 `<table><tbody><tr></tr></tbody></table>`。

### 无效 `ref` 属性

当 `ref` 属性包含异常值时我们将抛出此错误。这包括很久前即启用的字符串型 `refs`。
Expand Down
Loading