From b0ee6d82646f0dfbd4aea0299d658dea4d723467 Mon Sep 17 00:00:00 2001 From: ShenHdou <15574380273@163.com> Date: Sun, 21 Apr 2024 00:57:39 +0800 Subject: [PATCH 1/5] docs(demo): translate src/content/learn/Passing Data Deeply with Context --- .../learn/passing-data-deeply-with-context.md | 1165 +---------------- 1 file changed, 1 insertion(+), 1164 deletions(-) diff --git a/src/content/learn/passing-data-deeply-with-context.md b/src/content/learn/passing-data-deeply-with-context.md index 1aea87b35..e5cebbfdf 100644 --- a/src/content/learn/passing-data-deeply-with-context.md +++ b/src/content/learn/passing-data-deeply-with-context.md @@ -1,1164 +1 @@ ---- -title: Passing Data Deeply with Context ---- - - - -Usually, you will pass information from a parent component to a child component via props. But passing props can become verbose and inconvenient if you have to pass them through many components in the middle, or if many components in your app need the same information. *Context* lets the parent component make some information available to any component in the tree below it—no matter how deep—without passing it explicitly through props. - - - - - -- What "prop drilling" is -- How to replace repetitive prop passing with context -- Common use cases for context -- Common alternatives to context - - - -## The problem with passing props {/*the-problem-with-passing-props*/} - -[Passing props](/learn/passing-props-to-a-component) is a great way to explicitly pipe data through your UI tree to the components that use it. - -But passing props can become verbose and inconvenient when you need to pass some prop deeply through the tree, or if many components need the same prop. The nearest common ancestor could be far removed from the components that need data, and [lifting state up](/learn/sharing-state-between-components) that high can lead to a situation called "prop drilling". - - - - - -Lifting state up - - - - -Prop drilling - - - - - -Wouldn't it be great if there were a way to "teleport" data to the components in the tree that need it without passing props? With React's context feature, there is! - -## Context: an alternative to passing props {/*context-an-alternative-to-passing-props*/} - -Context lets a parent component provide data to the entire tree below it. There are many uses for context. Here is one example. Consider this `Heading` component that accepts a `level` for its size: - - - -```js -import Heading from './Heading.js'; -import Section from './Section.js'; - -export default function Page() { - return ( -
- Title - Heading - Sub-heading - Sub-sub-heading - Sub-sub-sub-heading - Sub-sub-sub-sub-heading -
- ); -} -``` - -```js src/Section.js -export default function Section({ children }) { - return ( -
- {children} -
- ); -} -``` - -```js src/Heading.js -export default function Heading({ level, children }) { - switch (level) { - case 1: - return

{children}

; - case 2: - return

{children}

; - case 3: - return

{children}

; - case 4: - return

{children}

; - case 5: - return
{children}
; - case 6: - return
{children}
; - default: - throw Error('Unknown level: ' + level); - } -} -``` - -```css -.section { - padding: 10px; - margin: 5px; - border-radius: 5px; - border: 1px solid #aaa; -} -``` - -
- -Let's say you want multiple headings within the same `Section` to always have the same size: - - - -```js -import Heading from './Heading.js'; -import Section from './Section.js'; - -export default function Page() { - return ( -
- Title -
- Heading - Heading - Heading -
- Sub-heading - Sub-heading - Sub-heading -
- Sub-sub-heading - Sub-sub-heading - Sub-sub-heading -
-
-
-
- ); -} -``` - -```js src/Section.js -export default function Section({ children }) { - return ( -
- {children} -
- ); -} -``` - -```js src/Heading.js -export default function Heading({ level, children }) { - switch (level) { - case 1: - return

{children}

; - case 2: - return

{children}

; - case 3: - return

{children}

; - case 4: - return

{children}

; - case 5: - return
{children}
; - case 6: - return
{children}
; - default: - throw Error('Unknown level: ' + level); - } -} -``` - -```css -.section { - padding: 10px; - margin: 5px; - border-radius: 5px; - border: 1px solid #aaa; -} -``` - -
- -Currently, you pass the `level` prop to each `` separately: - -```js -
- About - Photos - Videos -
-``` - -It would be nice if you could pass the `level` prop to the `
` component instead and remove it from the ``. This way you could enforce that all headings in the same section have the same size: - -```js -
- About - Photos - Videos -
-``` - -But how can the `` component know the level of its closest `
`? **That would require some way for a child to "ask" for data from somewhere above in the tree.** - -You can't do it with props alone. This is where context comes into play. You will do it in three steps: - -1. **Create** a context. (You can call it `LevelContext`, since it's for the heading level.) -2. **Use** that context from the component that needs the data. (`Heading` will use `LevelContext`.) -3. **Provide** that context from the component that specifies the data. (`Section` will provide `LevelContext`.) - -Context lets a parent--even a distant one!--provide some data to the entire tree inside of it. - - - - - -Using context in close children - - - - - -Using context in distant children - - - - - -### Step 1: Create the context {/*step-1-create-the-context*/} - -First, you need to create the context. You'll need to **export it from a file** so that your components can use it: - - - -```js -import Heading from './Heading.js'; -import Section from './Section.js'; - -export default function Page() { - return ( -
- Title -
- Heading - Heading - Heading -
- Sub-heading - Sub-heading - Sub-heading -
- Sub-sub-heading - Sub-sub-heading - Sub-sub-heading -
-
-
-
- ); -} -``` - -```js src/Section.js -export default function Section({ children }) { - return ( -
- {children} -
- ); -} -``` - -```js src/Heading.js -export default function Heading({ level, children }) { - switch (level) { - case 1: - return

{children}

; - case 2: - return

{children}

; - case 3: - return

{children}

; - case 4: - return

{children}

; - case 5: - return
{children}
; - case 6: - return
{children}
; - default: - throw Error('Unknown level: ' + level); - } -} -``` - -```js src/LevelContext.js active -import { createContext } from 'react'; - -export const LevelContext = createContext(1); -``` - -```css -.section { - padding: 10px; - margin: 5px; - border-radius: 5px; - border: 1px solid #aaa; -} -``` - -
- -The only argument to `createContext` is the _default_ value. Here, `1` refers to the biggest heading level, but you could pass any kind of value (even an object). You will see the significance of the default value in the next step. - -### Step 2: Use the context {/*step-2-use-the-context*/} - -Import the `useContext` Hook from React and your context: - -```js -import { useContext } from 'react'; -import { LevelContext } from './LevelContext.js'; -``` - -Currently, the `Heading` component reads `level` from props: - -```js -export default function Heading({ level, children }) { - // ... -} -``` - -Instead, remove the `level` prop and read the value from the context you just imported, `LevelContext`: - -```js {2} -export default function Heading({ children }) { - const level = useContext(LevelContext); - // ... -} -``` - -`useContext` is a Hook. Just like `useState` and `useReducer`, you can only call a Hook immediately inside a React component (not inside loops or conditions). **`useContext` tells React that the `Heading` component wants to read the `LevelContext`.** - -Now that the `Heading` component doesn't have a `level` prop, you don't need to pass the level prop to `Heading` in your JSX like this anymore: - -```js -
- Sub-sub-heading - Sub-sub-heading - Sub-sub-heading -
-``` - -Update the JSX so that it's the `Section` that receives it instead: - -```jsx -
- Sub-sub-heading - Sub-sub-heading - Sub-sub-heading -
-``` - -As a reminder, this is the markup that you were trying to get working: - - - -```js -import Heading from './Heading.js'; -import Section from './Section.js'; - -export default function Page() { - return ( -
- Title -
- Heading - Heading - Heading -
- Sub-heading - Sub-heading - Sub-heading -
- Sub-sub-heading - Sub-sub-heading - Sub-sub-heading -
-
-
-
- ); -} -``` - -```js src/Section.js -export default function Section({ children }) { - return ( -
- {children} -
- ); -} -``` - -```js src/Heading.js -import { useContext } from 'react'; -import { LevelContext } from './LevelContext.js'; - -export default function Heading({ children }) { - const level = useContext(LevelContext); - switch (level) { - case 1: - return

{children}

; - case 2: - return

{children}

; - case 3: - return

{children}

; - case 4: - return

{children}

; - case 5: - return
{children}
; - case 6: - return
{children}
; - default: - throw Error('Unknown level: ' + level); - } -} -``` - -```js src/LevelContext.js -import { createContext } from 'react'; - -export const LevelContext = createContext(1); -``` - -```css -.section { - padding: 10px; - margin: 5px; - border-radius: 5px; - border: 1px solid #aaa; -} -``` - -
- -Notice this example doesn't quite work, yet! All the headings have the same size because **even though you're *using* the context, you have not *provided* it yet.** React doesn't know where to get it! - -If you don't provide the context, React will use the default value you've specified in the previous step. In this example, you specified `1` as the argument to `createContext`, so `useContext(LevelContext)` returns `1`, setting all those headings to `

`. Let's fix this problem by having each `Section` provide its own context. - -### Step 3: Provide the context {/*step-3-provide-the-context*/} - -The `Section` component currently renders its children: - -```js -export default function Section({ children }) { - return ( -
- {children} -
- ); -} -``` - -**Wrap them with a context provider** to provide the `LevelContext` to them: - -```js {1,6,8} -import { LevelContext } from './LevelContext.js'; - -export default function Section({ level, children }) { - return ( -
- - {children} - -
- ); -} -``` - -This tells React: "if any component inside this `
` asks for `LevelContext`, give them this `level`." The component will use the value of the nearest `` in the UI tree above it. - - - -```js -import Heading from './Heading.js'; -import Section from './Section.js'; - -export default function Page() { - return ( -
- Title -
- Heading - Heading - Heading -
- Sub-heading - Sub-heading - Sub-heading -
- Sub-sub-heading - Sub-sub-heading - Sub-sub-heading -
-
-
-
- ); -} -``` - -```js src/Section.js -import { LevelContext } from './LevelContext.js'; - -export default function Section({ level, children }) { - return ( -
- - {children} - -
- ); -} -``` - -```js src/Heading.js -import { useContext } from 'react'; -import { LevelContext } from './LevelContext.js'; - -export default function Heading({ children }) { - const level = useContext(LevelContext); - switch (level) { - case 1: - return

{children}

; - case 2: - return

{children}

; - case 3: - return

{children}

; - case 4: - return

{children}

; - case 5: - return
{children}
; - case 6: - return
{children}
; - default: - throw Error('Unknown level: ' + level); - } -} -``` - -```js src/LevelContext.js -import { createContext } from 'react'; - -export const LevelContext = createContext(1); -``` - -```css -.section { - padding: 10px; - margin: 5px; - border-radius: 5px; - border: 1px solid #aaa; -} -``` - -
- -It's the same result as the original code, but you did not need to pass the `level` prop to each `Heading` component! Instead, it "figures out" its heading level by asking the closest `Section` above: - -1. You pass a `level` prop to the `
`. -2. `Section` wraps its children into ``. -3. `Heading` asks the closest value of `LevelContext` above with `useContext(LevelContext)`. - -## Using and providing context from the same component {/*using-and-providing-context-from-the-same-component*/} - -Currently, you still have to specify each section's `level` manually: - -```js -export default function Page() { - return ( -
- ... -
- ... -
- ... -``` - -Since context lets you read information from a component above, each `Section` could read the `level` from the `Section` above, and pass `level + 1` down automatically. Here is how you could do it: - -```js src/Section.js {5,8} -import { useContext } from 'react'; -import { LevelContext } from './LevelContext.js'; - -export default function Section({ children }) { - const level = useContext(LevelContext); - return ( -
- - {children} - -
- ); -} -``` - -With this change, you don't need to pass the `level` prop *either* to the `
` or to the ``: - - - -```js -import Heading from './Heading.js'; -import Section from './Section.js'; - -export default function Page() { - return ( -
- Title -
- Heading - Heading - Heading -
- Sub-heading - Sub-heading - Sub-heading -
- Sub-sub-heading - Sub-sub-heading - Sub-sub-heading -
-
-
-
- ); -} -``` - -```js src/Section.js -import { useContext } from 'react'; -import { LevelContext } from './LevelContext.js'; - -export default function Section({ children }) { - const level = useContext(LevelContext); - return ( -
- - {children} - -
- ); -} -``` - -```js src/Heading.js -import { useContext } from 'react'; -import { LevelContext } from './LevelContext.js'; - -export default function Heading({ children }) { - const level = useContext(LevelContext); - switch (level) { - case 0: - throw Error('Heading must be inside a Section!'); - case 1: - return

{children}

; - case 2: - return

{children}

; - case 3: - return

{children}

; - case 4: - return

{children}

; - case 5: - return
{children}
; - case 6: - return
{children}
; - default: - throw Error('Unknown level: ' + level); - } -} -``` - -```js src/LevelContext.js -import { createContext } from 'react'; - -export const LevelContext = createContext(0); -``` - -```css -.section { - padding: 10px; - margin: 5px; - border-radius: 5px; - border: 1px solid #aaa; -} -``` - -
- -Now both `Heading` and `Section` read the `LevelContext` to figure out how "deep" they are. And the `Section` wraps its children into the `LevelContext` to specify that anything inside of it is at a "deeper" level. - - - -This example uses heading levels because they show visually how nested components can override context. But context is useful for many other use cases too. You can pass down any information needed by the entire subtree: the current color theme, the currently logged in user, and so on. - - - -## Context passes through intermediate components {/*context-passes-through-intermediate-components*/} - -You can insert as many components as you like between the component that provides context and the one that uses it. This includes both built-in components like `
` and components you might build yourself. - -In this example, the same `Post` component (with a dashed border) is rendered at two different nesting levels. Notice that the `` inside of it gets its level automatically from the closest `
`: - - - -```js -import Heading from './Heading.js'; -import Section from './Section.js'; - -export default function ProfilePage() { - return ( -
- My Profile - - -
- ); -} - -function AllPosts() { - return ( -
- Posts - -
- ); -} - -function RecentPosts() { - return ( -
- Recent Posts - - -
- ); -} - -function Post({ title, body }) { - return ( -
- - {title} - -

{body}

-
- ); -} -``` - -```js src/Section.js -import { useContext } from 'react'; -import { LevelContext } from './LevelContext.js'; - -export default function Section({ children, isFancy }) { - const level = useContext(LevelContext); - return ( -
- - {children} - -
- ); -} -``` - -```js src/Heading.js -import { useContext } from 'react'; -import { LevelContext } from './LevelContext.js'; - -export default function Heading({ children }) { - const level = useContext(LevelContext); - switch (level) { - case 0: - throw Error('Heading must be inside a Section!'); - case 1: - return

{children}

; - case 2: - return

{children}

; - case 3: - return

{children}

; - case 4: - return

{children}

; - case 5: - return
{children}
; - case 6: - return
{children}
; - default: - throw Error('Unknown level: ' + level); - } -} -``` - -```js src/LevelContext.js -import { createContext } from 'react'; - -export const LevelContext = createContext(0); -``` - -```css -.section { - padding: 10px; - margin: 5px; - border-radius: 5px; - border: 1px solid #aaa; -} - -.fancy { - border: 4px dashed pink; -} -``` - -
- -You didn't do anything special for this to work. A `Section` specifies the context for the tree inside it, so you can insert a `` anywhere, and it will have the correct size. Try it in the sandbox above! - -**Context lets you write components that "adapt to their surroundings" and display themselves differently depending on _where_ (or, in other words, _in which context_) they are being rendered.** - -How context works might remind you of [CSS property inheritance.](https://developer.mozilla.org/en-US/docs/Web/CSS/inheritance) In CSS, you can specify `color: blue` for a `
`, and any DOM node inside of it, no matter how deep, will inherit that color unless some other DOM node in the middle overrides it with `color: green`. Similarly, in React, the only way to override some context coming from above is to wrap children into a context provider with a different value. - -In CSS, different properties like `color` and `background-color` don't override each other. You can set all `
`'s `color` to red without impacting `background-color`. Similarly, **different React contexts don't override each other.** Each context that you make with `createContext()` is completely separate from other ones, and ties together components using and providing *that particular* context. One component may use or provide many different contexts without a problem. - -## Before you use context {/*before-you-use-context*/} - -Context is very tempting to use! However, this also means it's too easy to overuse it. **Just because you need to pass some props several levels deep doesn't mean you should put that information into context.** - -Here's a few alternatives you should consider before using context: - -1. **Start by [passing props.](/learn/passing-props-to-a-component)** If your components are not trivial, it's not unusual to pass a dozen props down through a dozen components. It may feel like a slog, but it makes it very clear which components use which data! The person maintaining your code will be glad you've made the data flow explicit with props. -2. **Extract components and [pass JSX as `children`](/learn/passing-props-to-a-component#passing-jsx-as-children) to them.** If you pass some data through many layers of intermediate components that don't use that data (and only pass it further down), this often means that you forgot to extract some components along the way. For example, maybe you pass data props like `posts` to visual components that don't use them directly, like ``. Instead, make `Layout` take `children` as a prop, and render ``. This reduces the number of layers between the component specifying the data and the one that needs it. - -If neither of these approaches works well for you, consider context. - -## Use cases for context {/*use-cases-for-context*/} - -* **Theming:** If your app lets the user change its appearance (e.g. dark mode), you can put a context provider at the top of your app, and use that context in components that need to adjust their visual look. -* **Current account:** Many components might need to know the currently logged in user. Putting it in context makes it convenient to read it anywhere in the tree. Some apps also let you operate multiple accounts at the same time (e.g. to leave a comment as a different user). In those cases, it can be convenient to wrap a part of the UI into a nested provider with a different current account value. -* **Routing:** Most routing solutions use context internally to hold the current route. This is how every link "knows" whether it's active or not. If you build your own router, you might want to do it too. -* **Managing state:** As your app grows, you might end up with a lot of state closer to the top of your app. Many distant components below may want to change it. It is common to [use a reducer together with context](/learn/scaling-up-with-reducer-and-context) to manage complex state and pass it down to distant components without too much hassle. - -Context is not limited to static values. If you pass a different value on the next render, React will update all the components reading it below! This is why context is often used in combination with state. - -In general, if some information is needed by distant components in different parts of the tree, it's a good indication that context will help you. - - - -* Context lets a component provide some information to the entire tree below it. -* To pass context: - 1. Create and export it with `export const MyContext = createContext(defaultValue)`. - 2. Pass it to the `useContext(MyContext)` Hook to read it in any child component, no matter how deep. - 3. Wrap children into `` to provide it from a parent. -* Context passes through any components in the middle. -* Context lets you write components that "adapt to their surroundings". -* Before you use context, try passing props or passing JSX as `children`. - - - - - -#### Replace prop drilling with context {/*replace-prop-drilling-with-context*/} - -In this example, toggling the checkbox changes the `imageSize` prop passed to each ``. The checkbox state is held in the top-level `App` component, but each `` needs to be aware of it. - -Currently, `App` passes `imageSize` to `List`, which passes it to each `Place`, which passes it to the `PlaceImage`. Remove the `imageSize` prop, and instead pass it from the `App` component directly to `PlaceImage`. - -You can declare context in `Context.js`. - - - -```js src/App.js -import { useState } from 'react'; -import { places } from './data.js'; -import { getImageUrl } from './utils.js'; - -export default function App() { - const [isLarge, setIsLarge] = useState(false); - const imageSize = isLarge ? 150 : 100; - return ( - <> - -
- - - ) -} - -function List({ imageSize }) { - const listItems = places.map(place => -
  • - -
  • - ); - return
      {listItems}
    ; -} - -function Place({ place, imageSize }) { - return ( - <> - -

    - {place.name} - {': ' + place.description} -

    - - ); -} - -function PlaceImage({ place, imageSize }) { - return ( - {place.name} - ); -} -``` - -```js src/Context.js - -``` - -```js src/data.js -export const places = [{ - id: 0, - name: 'Bo-Kaap in Cape Town, South Africa', - description: 'The tradition of choosing bright colors for houses began in the late 20th century.', - imageId: 'K9HVAGH' -}, { - id: 1, - name: 'Rainbow Village in Taichung, Taiwan', - description: 'To save the houses from demolition, Huang Yung-Fu, a local resident, painted all 1,200 of them in 1924.', - imageId: '9EAYZrt' -}, { - id: 2, - name: 'Macromural de Pachuca, Mexico', - description: 'One of the largest murals in the world covering homes in a hillside neighborhood.', - imageId: 'DgXHVwu' -}, { - id: 3, - name: 'Selarón Staircase in Rio de Janeiro, Brazil', - description: 'This landmark was created by Jorge Selarón, a Chilean-born artist, as a "tribute to the Brazilian people."', - imageId: 'aeO3rpI' -}, { - id: 4, - name: 'Burano, Italy', - description: 'The houses are painted following a specific color system dating back to 16th century.', - imageId: 'kxsph5C' -}, { - id: 5, - name: 'Chefchaouen, Marocco', - description: 'There are a few theories on why the houses are painted blue, including that the color repels mosquitos or that it symbolizes sky and heaven.', - imageId: 'rTqKo46' -}, { - id: 6, - name: 'Gamcheon Culture Village in Busan, South Korea', - description: 'In 2009, the village was converted into a cultural hub by painting the houses and featuring exhibitions and art installations.', - imageId: 'ZfQOOzf' -}]; -``` - -```js src/utils.js -export function getImageUrl(place) { - return ( - 'https://i.imgur.com/' + - place.imageId + - 'l.jpg' - ); -} -``` - -```css -ul { list-style-type: none; padding: 0px 10px; } -li { - margin-bottom: 10px; - display: grid; - grid-template-columns: auto 1fr; - gap: 20px; - align-items: center; -} -``` - -
    - - - -Remove `imageSize` prop from all the components. - -Create and export `ImageSizeContext` from `Context.js`. Then wrap the List into `` to pass the value down, and `useContext(ImageSizeContext)` to read it in the `PlaceImage`: - - - -```js src/App.js -import { useState, useContext } from 'react'; -import { places } from './data.js'; -import { getImageUrl } from './utils.js'; -import { ImageSizeContext } from './Context.js'; - -export default function App() { - const [isLarge, setIsLarge] = useState(false); - const imageSize = isLarge ? 150 : 100; - return ( - - -
    - -
    - ) -} - -function List() { - const listItems = places.map(place => -
  • - -
  • - ); - return
      {listItems}
    ; -} - -function Place({ place }) { - return ( - <> - -

    - {place.name} - {': ' + place.description} -

    - - ); -} - -function PlaceImage({ place }) { - const imageSize = useContext(ImageSizeContext); - return ( - {place.name} - ); -} -``` - -```js src/Context.js -import { createContext } from 'react'; - -export const ImageSizeContext = createContext(500); -``` - -```js src/data.js -export const places = [{ - id: 0, - name: 'Bo-Kaap in Cape Town, South Africa', - description: 'The tradition of choosing bright colors for houses began in the late 20th century.', - imageId: 'K9HVAGH' -}, { - id: 1, - name: 'Rainbow Village in Taichung, Taiwan', - description: 'To save the houses from demolition, Huang Yung-Fu, a local resident, painted all 1,200 of them in 1924.', - imageId: '9EAYZrt' -}, { - id: 2, - name: 'Macromural de Pachuca, Mexico', - description: 'One of the largest murals in the world covering homes in a hillside neighborhood.', - imageId: 'DgXHVwu' -}, { - id: 3, - name: 'Selarón Staircase in Rio de Janeiro, Brazil', - description: 'This landmark was created by Jorge Selarón, a Chilean-born artist, as a "tribute to the Brazilian people".', - imageId: 'aeO3rpI' -}, { - id: 4, - name: 'Burano, Italy', - description: 'The houses are painted following a specific color system dating back to 16th century.', - imageId: 'kxsph5C' -}, { - id: 5, - name: 'Chefchaouen, Marocco', - description: 'There are a few theories on why the houses are painted blue, including that the color repels mosquitos or that it symbolizes sky and heaven.', - imageId: 'rTqKo46' -}, { - id: 6, - name: 'Gamcheon Culture Village in Busan, South Korea', - description: 'In 2009, the village was converted into a cultural hub by painting the houses and featuring exhibitions and art installations.', - imageId: 'ZfQOOzf' -}]; -``` - -```js src/utils.js -export function getImageUrl(place) { - return ( - 'https://i.imgur.com/' + - place.imageId + - 'l.jpg' - ); -} -``` - -```css -ul { list-style-type: none; padding: 0px 10px; } -li { - margin-bottom: 10px; - display: grid; - grid-template-columns: auto 1fr; - gap: 20px; - align-items: center; -} -``` - -
    - -Note how components in the middle don't need to pass `imageSize` anymore. - -
    - -
    +--- title: 使用Context深層傳遞參數 --- Usually, you will pass information from a parent component to a child component via props. But passing props can become verbose and inconvenient if you have to pass them through many components in the middle, or if many components in your app need the same information. *Context* lets the parent component make some information available to any component in the tree below it—no matter how deep—without passing it explicitly through props. - What "prop drilling" is - How to replace repetitive prop passing with context - Common use cases for context - Common alternatives to context ## The problem with passing props {/*the-problem-with-passing-props*/} [Passing props](/learn/passing-props-to-a-component) is a great way to explicitly pipe data through your UI tree to the components that use it. But passing props can become verbose and inconvenient when you need to pass some prop deeply through the tree, or if many components need the same prop. The nearest common ancestor could be far removed from the components that need data, and [lifting state up](/learn/sharing-state-between-components) that high can lead to a situation called "prop drilling". Lifting state up Prop drilling Wouldn't it be great if there were a way to "teleport" data to the components in the tree that need it without passing props? With React's context feature, there is! ## Context: an alternative to passing props {/*context-an-alternative-to-passing-props*/} Context lets a parent component provide data to the entire tree below it. There are many uses for context. Here is one example. Consider this `Heading` component that accepts a `level` for its size: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title Heading Sub-heading Sub-sub-heading Sub-sub-sub-heading Sub-sub-sub-sub-heading
    ); } ``` ```js src/Section.js export default function Section({ children }) { return (
    {children}
    ); } ``` ```js src/Heading.js export default function Heading({ level, children }) { switch (level) { case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    Let's say you want multiple headings within the same `Section` to always have the same size: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title
    Heading Heading Heading
    Sub-heading Sub-heading Sub-heading
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ); } ``` ```js src/Section.js export default function Section({ children }) { return (
    {children}
    ); } ``` ```js src/Heading.js export default function Heading({ level, children }) { switch (level) { case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    Currently, you pass the `level` prop to each `` separately: ```js
    About Photos Videos
    ``` It would be nice if you could pass the `level` prop to the `
    ` component instead and remove it from the ``. This way you could enforce that all headings in the same section have the same size: ```js
    About Photos Videos
    ``` But how can the `` component know the level of its closest `
    `? **That would require some way for a child to "ask" for data from somewhere above in the tree.** You can't do it with props alone. This is where context comes into play. You will do it in three steps: 1. **Create** a context. (You can call it `LevelContext`, since it's for the heading level.) 2. **Use** that context from the component that needs the data. (`Heading` will use `LevelContext`.) 3. **Provide** that context from the component that specifies the data. (`Section` will provide `LevelContext`.) Context lets a parent--even a distant one!--provide some data to the entire tree inside of it. Using context in close children Using context in distant children ### Step 1: Create the context {/*step-1-create-the-context*/} First, you need to create the context. You'll need to **export it from a file** so that your components can use it: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title
    Heading Heading Heading
    Sub-heading Sub-heading Sub-heading
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ); } ``` ```js src/Section.js export default function Section({ children }) { return (
    {children}
    ); } ``` ```js src/Heading.js export default function Heading({ level, children }) { switch (level) { case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```js src/LevelContext.js active import { createContext } from 'react'; export const LevelContext = createContext(1); ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    The only argument to `createContext` is the _default_ value. Here, `1` refers to the biggest heading level, but you could pass any kind of value (even an object). You will see the significance of the default value in the next step. ### Step 2: Use the context {/*step-2-use-the-context*/} Import the `useContext` Hook from React and your context: ```js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; ``` Currently, the `Heading` component reads `level` from props: ```js export default function Heading({ level, children }) { // ... } ``` Instead, remove the `level` prop and read the value from the context you just imported, `LevelContext`: ```js {2} export default function Heading({ children }) { const level = useContext(LevelContext); // ... } ``` `useContext` is a Hook. Just like `useState` and `useReducer`, you can only call a Hook immediately inside a React component (not inside loops or conditions). **`useContext` tells React that the `Heading` component wants to read the `LevelContext`.** Now that the `Heading` component doesn't have a `level` prop, you don't need to pass the level prop to `Heading` in your JSX like this anymore: ```js
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ``` Update the JSX so that it's the `Section` that receives it instead: ```jsx
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ``` As a reminder, this is the markup that you were trying to get working: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title
    Heading Heading Heading
    Sub-heading Sub-heading Sub-heading
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ); } ``` ```js src/Section.js export default function Section({ children }) { return (
    {children}
    ); } ``` ```js src/Heading.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Heading({ children }) { const level = useContext(LevelContext); switch (level) { case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```js src/LevelContext.js import { createContext } from 'react'; export const LevelContext = createContext(1); ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    Notice this example doesn't quite work, yet! All the headings have the same size because **even though you're *using* the context, you have not *provided* it yet.** React doesn't know where to get it! If you don't provide the context, React will use the default value you've specified in the previous step. In this example, you specified `1` as the argument to `createContext`, so `useContext(LevelContext)` returns `1`, setting all those headings to `

    `. Let's fix this problem by having each `Section` provide its own context. ### Step 3: Provide the context {/*step-3-provide-the-context*/} The `Section` component currently renders its children: ```js export default function Section({ children }) { return (
    {children}
    ); } ``` **Wrap them with a context provider** to provide the `LevelContext` to them: ```js {1,6,8} import { LevelContext } from './LevelContext.js'; export default function Section({ level, children }) { return (
    {children}
    ); } ``` This tells React: "if any component inside this `
    ` asks for `LevelContext`, give them this `level`." The component will use the value of the nearest `` in the UI tree above it. ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title
    Heading Heading Heading
    Sub-heading Sub-heading Sub-heading
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ); } ``` ```js src/Section.js import { LevelContext } from './LevelContext.js'; export default function Section({ level, children }) { return (
    {children}
    ); } ``` ```js src/Heading.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Heading({ children }) { const level = useContext(LevelContext); switch (level) { case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```js src/LevelContext.js import { createContext } from 'react'; export const LevelContext = createContext(1); ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    It's the same result as the original code, but you did not need to pass the `level` prop to each `Heading` component! Instead, it "figures out" its heading level by asking the closest `Section` above: 1. You pass a `level` prop to the `
    `. 2. `Section` wraps its children into ``. 3. `Heading` asks the closest value of `LevelContext` above with `useContext(LevelContext)`. ## Using and providing context from the same component {/*using-and-providing-context-from-the-same-component*/} Currently, you still have to specify each section's `level` manually: ```js export default function Page() { return (
    ...
    ...
    ... ``` Since context lets you read information from a component above, each `Section` could read the `level` from the `Section` above, and pass `level + 1` down automatically. Here is how you could do it: ```js src/Section.js {5,8} import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Section({ children }) { const level = useContext(LevelContext); return (
    {children}
    ); } ``` With this change, you don't need to pass the `level` prop *either* to the `
    ` or to the ``: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title
    Heading Heading Heading
    Sub-heading Sub-heading Sub-heading
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ); } ``` ```js src/Section.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Section({ children }) { const level = useContext(LevelContext); return (
    {children}
    ); } ``` ```js src/Heading.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Heading({ children }) { const level = useContext(LevelContext); switch (level) { case 0: throw Error('Heading must be inside a Section!'); case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```js src/LevelContext.js import { createContext } from 'react'; export const LevelContext = createContext(0); ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    Now both `Heading` and `Section` read the `LevelContext` to figure out how "deep" they are. And the `Section` wraps its children into the `LevelContext` to specify that anything inside of it is at a "deeper" level. This example uses heading levels because they show visually how nested components can override context. But context is useful for many other use cases too. You can pass down any information needed by the entire subtree: the current color theme, the currently logged in user, and so on. ## Context passes through intermediate components {/*context-passes-through-intermediate-components*/} You can insert as many components as you like between the component that provides context and the one that uses it. This includes both built-in components like `
    ` and components you might build yourself. In this example, the same `Post` component (with a dashed border) is rendered at two different nesting levels. Notice that the `` inside of it gets its level automatically from the closest `
    `: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function ProfilePage() { return (
    My Profile
    ); } function AllPosts() { return (
    Posts
    ); } function RecentPosts() { return (
    Recent Posts
    ); } function Post({ title, body }) { return (
    {title}

    {body}

    ); } ``` ```js src/Section.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Section({ children, isFancy }) { const level = useContext(LevelContext); return (
    {children}
    ); } ``` ```js src/Heading.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Heading({ children }) { const level = useContext(LevelContext); switch (level) { case 0: throw Error('Heading must be inside a Section!'); case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```js src/LevelContext.js import { createContext } from 'react'; export const LevelContext = createContext(0); ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } .fancy { border: 4px dashed pink; } ```
    You didn't do anything special for this to work. A `Section` specifies the context for the tree inside it, so you can insert a `` anywhere, and it will have the correct size. Try it in the sandbox above! **Context lets you write components that "adapt to their surroundings" and display themselves differently depending on _where_ (or, in other words, _in which context_) they are being rendered.** How context works might remind you of [CSS property inheritance.](https://developer.mozilla.org/en-US/docs/Web/CSS/inheritance) In CSS, you can specify `color: blue` for a `
    `, and any DOM node inside of it, no matter how deep, will inherit that color unless some other DOM node in the middle overrides it with `color: green`. Similarly, in React, the only way to override some context coming from above is to wrap children into a context provider with a different value. In CSS, different properties like `color` and `background-color` don't override each other. You can set all `
    `'s `color` to red without impacting `background-color`. Similarly, **different React contexts don't override each other.** Each context that you make with `createContext()` is completely separate from other ones, and ties together components using and providing *that particular* context. One component may use or provide many different contexts without a problem. ## Before you use context {/*before-you-use-context*/} Context is very tempting to use! However, this also means it's too easy to overuse it. **Just because you need to pass some props several levels deep doesn't mean you should put that information into context.** Here's a few alternatives you should consider before using context: 1. **Start by [passing props.](/learn/passing-props-to-a-component)** If your components are not trivial, it's not unusual to pass a dozen props down through a dozen components. It may feel like a slog, but it makes it very clear which components use which data! The person maintaining your code will be glad you've made the data flow explicit with props. 2. **Extract components and [pass JSX as `children`](/learn/passing-props-to-a-component#passing-jsx-as-children) to them.** If you pass some data through many layers of intermediate components that don't use that data (and only pass it further down), this often means that you forgot to extract some components along the way. For example, maybe you pass data props like `posts` to visual components that don't use them directly, like ``. Instead, make `Layout` take `children` as a prop, and render ``. This reduces the number of layers between the component specifying the data and the one that needs it. If neither of these approaches works well for you, consider context. ## Use cases for context {/*use-cases-for-context*/} * **Theming:** If your app lets the user change its appearance (e.g. dark mode), you can put a context provider at the top of your app, and use that context in components that need to adjust their visual look. * **Current account:** Many components might need to know the currently logged in user. Putting it in context makes it convenient to read it anywhere in the tree. Some apps also let you operate multiple accounts at the same time (e.g. to leave a comment as a different user). In those cases, it can be convenient to wrap a part of the UI into a nested provider with a different current account value. * **Routing:** Most routing solutions use context internally to hold the current route. This is how every link "knows" whether it's active or not. If you build your own router, you might want to do it too. * **Managing state:** As your app grows, you might end up with a lot of state closer to the top of your app. Many distant components below may want to change it. It is common to [use a reducer together with context](/learn/scaling-up-with-reducer-and-context) to manage complex state and pass it down to distant components without too much hassle. Context is not limited to static values. If you pass a different value on the next render, React will update all the components reading it below! This is why context is often used in combination with state. In general, if some information is needed by distant components in different parts of the tree, it's a good indication that context will help you. * Context lets a component provide some information to the entire tree below it. * To pass context: 1. Create and export it with `export const MyContext = createContext(defaultValue)`. 2. Pass it to the `useContext(MyContext)` Hook to read it in any child component, no matter how deep. 3. Wrap children into `` to provide it from a parent. * Context passes through any components in the middle. * Context lets you write components that "adapt to their surroundings". * Before you use context, try passing props or passing JSX as `children`. #### Replace prop drilling with context {/*replace-prop-drilling-with-context*/} In this example, toggling the checkbox changes the `imageSize` prop passed to each ``. The checkbox state is held in the top-level `App` component, but each `` needs to be aware of it. Currently, `App` passes `imageSize` to `List`, which passes it to each `Place`, which passes it to the `PlaceImage`. Remove the `imageSize` prop, and instead pass it from the `App` component directly to `PlaceImage`. You can declare context in `Context.js`. ```js src/App.js import { useState } from 'react'; import { places } from './data.js'; import { getImageUrl } from './utils.js'; export default function App() { const [isLarge, setIsLarge] = useState(false); const imageSize = isLarge ? 150 : 100; return ( <>
    ) } function List({ imageSize }) { const listItems = places.map(place =>
  • ); return
      {listItems}
    ; } function Place({ place, imageSize }) { return ( <>

    {place.name} {': ' + place.description}

    ); } function PlaceImage({ place, imageSize }) { return ( {place.name} ); } ``` ```js src/Context.js ``` ```js src/data.js export const places = [{ id: 0, name: 'Bo-Kaap in Cape Town, South Africa', description: 'The tradition of choosing bright colors for houses began in the late 20th century.', imageId: 'K9HVAGH' }, { id: 1, name: 'Rainbow Village in Taichung, Taiwan', description: 'To save the houses from demolition, Huang Yung-Fu, a local resident, painted all 1,200 of them in 1924.', imageId: '9EAYZrt' }, { id: 2, name: 'Macromural de Pachuca, Mexico', description: 'One of the largest murals in the world covering homes in a hillside neighborhood.', imageId: 'DgXHVwu' }, { id: 3, name: 'Selarón Staircase in Rio de Janeiro, Brazil', description: 'This landmark was created by Jorge Selarón, a Chilean-born artist, as a "tribute to the Brazilian people."', imageId: 'aeO3rpI' }, { id: 4, name: 'Burano, Italy', description: 'The houses are painted following a specific color system dating back to 16th century.', imageId: 'kxsph5C' }, { id: 5, name: 'Chefchaouen, Marocco', description: 'There are a few theories on why the houses are painted blue, including that the color repels mosquitos or that it symbolizes sky and heaven.', imageId: 'rTqKo46' }, { id: 6, name: 'Gamcheon Culture Village in Busan, South Korea', description: 'In 2009, the village was converted into a cultural hub by painting the houses and featuring exhibitions and art installations.', imageId: 'ZfQOOzf' }]; ``` ```js src/utils.js export function getImageUrl(place) { return ( 'https://i.imgur.com/' + place.imageId + 'l.jpg' ); } ``` ```css ul { list-style-type: none; padding: 0px 10px; } li { margin-bottom: 10px; display: grid; grid-template-columns: auto 1fr; gap: 20px; align-items: center; } ```
    Remove `imageSize` prop from all the components. Create and export `ImageSizeContext` from `Context.js`. Then wrap the List into `` to pass the value down, and `useContext(ImageSizeContext)` to read it in the `PlaceImage`: ```js src/App.js import { useState, useContext } from 'react'; import { places } from './data.js'; import { getImageUrl } from './utils.js'; import { ImageSizeContext } from './Context.js'; export default function App() { const [isLarge, setIsLarge] = useState(false); const imageSize = isLarge ? 150 : 100; return (
    ) } function List() { const listItems = places.map(place =>
  • ); return
      {listItems}
    ; } function Place({ place }) { return ( <>

    {place.name} {': ' + place.description}

    ); } function PlaceImage({ place }) { const imageSize = useContext(ImageSizeContext); return ( {place.name} ); } ``` ```js src/Context.js import { createContext } from 'react'; export const ImageSizeContext = createContext(500); ``` ```js src/data.js export const places = [{ id: 0, name: 'Bo-Kaap in Cape Town, South Africa', description: 'The tradition of choosing bright colors for houses began in the late 20th century.', imageId: 'K9HVAGH' }, { id: 1, name: 'Rainbow Village in Taichung, Taiwan', description: 'To save the houses from demolition, Huang Yung-Fu, a local resident, painted all 1,200 of them in 1924.', imageId: '9EAYZrt' }, { id: 2, name: 'Macromural de Pachuca, Mexico', description: 'One of the largest murals in the world covering homes in a hillside neighborhood.', imageId: 'DgXHVwu' }, { id: 3, name: 'Selarón Staircase in Rio de Janeiro, Brazil', description: 'This landmark was created by Jorge Selarón, a Chilean-born artist, as a "tribute to the Brazilian people".', imageId: 'aeO3rpI' }, { id: 4, name: 'Burano, Italy', description: 'The houses are painted following a specific color system dating back to 16th century.', imageId: 'kxsph5C' }, { id: 5, name: 'Chefchaouen, Marocco', description: 'There are a few theories on why the houses are painted blue, including that the color repels mosquitos or that it symbolizes sky and heaven.', imageId: 'rTqKo46' }, { id: 6, name: 'Gamcheon Culture Village in Busan, South Korea', description: 'In 2009, the village was converted into a cultural hub by painting the houses and featuring exhibitions and art installations.', imageId: 'ZfQOOzf' }]; ``` ```js src/utils.js export function getImageUrl(place) { return ( 'https://i.imgur.com/' + place.imageId + 'l.jpg' ); } ``` ```css ul { list-style-type: none; padding: 0px 10px; } li { margin-bottom: 10px; display: grid; grid-template-columns: auto 1fr; gap: 20px; align-items: center; } ```
    Note how components in the middle don't need to pass `imageSize` anymore.
    \ No newline at end of file From 97efbcbc83018012d9e90838c97e4205da1a1d98 Mon Sep 17 00:00:00 2001 From: ShenHdou <15574380273@163.com> Date: Mon, 22 Apr 2024 23:45:32 +0800 Subject: [PATCH 2/5] docs: translate src/content/learn/passing-data-deeply-with-context.md --- src/content/learn/passing-data-deeply-with-context.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/passing-data-deeply-with-context.md b/src/content/learn/passing-data-deeply-with-context.md index e5cebbfdf..36cceb6b5 100644 --- a/src/content/learn/passing-data-deeply-with-context.md +++ b/src/content/learn/passing-data-deeply-with-context.md @@ -1 +1 @@ ---- title: 使用Context深層傳遞參數 --- Usually, you will pass information from a parent component to a child component via props. But passing props can become verbose and inconvenient if you have to pass them through many components in the middle, or if many components in your app need the same information. *Context* lets the parent component make some information available to any component in the tree below it—no matter how deep—without passing it explicitly through props. - What "prop drilling" is - How to replace repetitive prop passing with context - Common use cases for context - Common alternatives to context ## The problem with passing props {/*the-problem-with-passing-props*/} [Passing props](/learn/passing-props-to-a-component) is a great way to explicitly pipe data through your UI tree to the components that use it. But passing props can become verbose and inconvenient when you need to pass some prop deeply through the tree, or if many components need the same prop. The nearest common ancestor could be far removed from the components that need data, and [lifting state up](/learn/sharing-state-between-components) that high can lead to a situation called "prop drilling". Lifting state up Prop drilling Wouldn't it be great if there were a way to "teleport" data to the components in the tree that need it without passing props? With React's context feature, there is! ## Context: an alternative to passing props {/*context-an-alternative-to-passing-props*/} Context lets a parent component provide data to the entire tree below it. There are many uses for context. Here is one example. Consider this `Heading` component that accepts a `level` for its size: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title Heading Sub-heading Sub-sub-heading Sub-sub-sub-heading Sub-sub-sub-sub-heading
    ); } ``` ```js src/Section.js export default function Section({ children }) { return (
    {children}
    ); } ``` ```js src/Heading.js export default function Heading({ level, children }) { switch (level) { case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    Let's say you want multiple headings within the same `Section` to always have the same size: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title
    Heading Heading Heading
    Sub-heading Sub-heading Sub-heading
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ); } ``` ```js src/Section.js export default function Section({ children }) { return (
    {children}
    ); } ``` ```js src/Heading.js export default function Heading({ level, children }) { switch (level) { case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    Currently, you pass the `level` prop to each `` separately: ```js
    About Photos Videos
    ``` It would be nice if you could pass the `level` prop to the `
    ` component instead and remove it from the ``. This way you could enforce that all headings in the same section have the same size: ```js
    About Photos Videos
    ``` But how can the `` component know the level of its closest `
    `? **That would require some way for a child to "ask" for data from somewhere above in the tree.** You can't do it with props alone. This is where context comes into play. You will do it in three steps: 1. **Create** a context. (You can call it `LevelContext`, since it's for the heading level.) 2. **Use** that context from the component that needs the data. (`Heading` will use `LevelContext`.) 3. **Provide** that context from the component that specifies the data. (`Section` will provide `LevelContext`.) Context lets a parent--even a distant one!--provide some data to the entire tree inside of it. Using context in close children Using context in distant children ### Step 1: Create the context {/*step-1-create-the-context*/} First, you need to create the context. You'll need to **export it from a file** so that your components can use it: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title
    Heading Heading Heading
    Sub-heading Sub-heading Sub-heading
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ); } ``` ```js src/Section.js export default function Section({ children }) { return (
    {children}
    ); } ``` ```js src/Heading.js export default function Heading({ level, children }) { switch (level) { case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```js src/LevelContext.js active import { createContext } from 'react'; export const LevelContext = createContext(1); ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    The only argument to `createContext` is the _default_ value. Here, `1` refers to the biggest heading level, but you could pass any kind of value (even an object). You will see the significance of the default value in the next step. ### Step 2: Use the context {/*step-2-use-the-context*/} Import the `useContext` Hook from React and your context: ```js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; ``` Currently, the `Heading` component reads `level` from props: ```js export default function Heading({ level, children }) { // ... } ``` Instead, remove the `level` prop and read the value from the context you just imported, `LevelContext`: ```js {2} export default function Heading({ children }) { const level = useContext(LevelContext); // ... } ``` `useContext` is a Hook. Just like `useState` and `useReducer`, you can only call a Hook immediately inside a React component (not inside loops or conditions). **`useContext` tells React that the `Heading` component wants to read the `LevelContext`.** Now that the `Heading` component doesn't have a `level` prop, you don't need to pass the level prop to `Heading` in your JSX like this anymore: ```js
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ``` Update the JSX so that it's the `Section` that receives it instead: ```jsx
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ``` As a reminder, this is the markup that you were trying to get working: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title
    Heading Heading Heading
    Sub-heading Sub-heading Sub-heading
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ); } ``` ```js src/Section.js export default function Section({ children }) { return (
    {children}
    ); } ``` ```js src/Heading.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Heading({ children }) { const level = useContext(LevelContext); switch (level) { case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```js src/LevelContext.js import { createContext } from 'react'; export const LevelContext = createContext(1); ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    Notice this example doesn't quite work, yet! All the headings have the same size because **even though you're *using* the context, you have not *provided* it yet.** React doesn't know where to get it! If you don't provide the context, React will use the default value you've specified in the previous step. In this example, you specified `1` as the argument to `createContext`, so `useContext(LevelContext)` returns `1`, setting all those headings to `

    `. Let's fix this problem by having each `Section` provide its own context. ### Step 3: Provide the context {/*step-3-provide-the-context*/} The `Section` component currently renders its children: ```js export default function Section({ children }) { return (
    {children}
    ); } ``` **Wrap them with a context provider** to provide the `LevelContext` to them: ```js {1,6,8} import { LevelContext } from './LevelContext.js'; export default function Section({ level, children }) { return (
    {children}
    ); } ``` This tells React: "if any component inside this `
    ` asks for `LevelContext`, give them this `level`." The component will use the value of the nearest `` in the UI tree above it. ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title
    Heading Heading Heading
    Sub-heading Sub-heading Sub-heading
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ); } ``` ```js src/Section.js import { LevelContext } from './LevelContext.js'; export default function Section({ level, children }) { return (
    {children}
    ); } ``` ```js src/Heading.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Heading({ children }) { const level = useContext(LevelContext); switch (level) { case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```js src/LevelContext.js import { createContext } from 'react'; export const LevelContext = createContext(1); ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    It's the same result as the original code, but you did not need to pass the `level` prop to each `Heading` component! Instead, it "figures out" its heading level by asking the closest `Section` above: 1. You pass a `level` prop to the `
    `. 2. `Section` wraps its children into ``. 3. `Heading` asks the closest value of `LevelContext` above with `useContext(LevelContext)`. ## Using and providing context from the same component {/*using-and-providing-context-from-the-same-component*/} Currently, you still have to specify each section's `level` manually: ```js export default function Page() { return (
    ...
    ...
    ... ``` Since context lets you read information from a component above, each `Section` could read the `level` from the `Section` above, and pass `level + 1` down automatically. Here is how you could do it: ```js src/Section.js {5,8} import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Section({ children }) { const level = useContext(LevelContext); return (
    {children}
    ); } ``` With this change, you don't need to pass the `level` prop *either* to the `
    ` or to the ``: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title
    Heading Heading Heading
    Sub-heading Sub-heading Sub-heading
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ); } ``` ```js src/Section.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Section({ children }) { const level = useContext(LevelContext); return (
    {children}
    ); } ``` ```js src/Heading.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Heading({ children }) { const level = useContext(LevelContext); switch (level) { case 0: throw Error('Heading must be inside a Section!'); case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```js src/LevelContext.js import { createContext } from 'react'; export const LevelContext = createContext(0); ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    Now both `Heading` and `Section` read the `LevelContext` to figure out how "deep" they are. And the `Section` wraps its children into the `LevelContext` to specify that anything inside of it is at a "deeper" level. This example uses heading levels because they show visually how nested components can override context. But context is useful for many other use cases too. You can pass down any information needed by the entire subtree: the current color theme, the currently logged in user, and so on. ## Context passes through intermediate components {/*context-passes-through-intermediate-components*/} You can insert as many components as you like between the component that provides context and the one that uses it. This includes both built-in components like `
    ` and components you might build yourself. In this example, the same `Post` component (with a dashed border) is rendered at two different nesting levels. Notice that the `` inside of it gets its level automatically from the closest `
    `: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function ProfilePage() { return (
    My Profile
    ); } function AllPosts() { return (
    Posts
    ); } function RecentPosts() { return (
    Recent Posts
    ); } function Post({ title, body }) { return (
    {title}

    {body}

    ); } ``` ```js src/Section.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Section({ children, isFancy }) { const level = useContext(LevelContext); return (
    {children}
    ); } ``` ```js src/Heading.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Heading({ children }) { const level = useContext(LevelContext); switch (level) { case 0: throw Error('Heading must be inside a Section!'); case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```js src/LevelContext.js import { createContext } from 'react'; export const LevelContext = createContext(0); ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } .fancy { border: 4px dashed pink; } ```
    You didn't do anything special for this to work. A `Section` specifies the context for the tree inside it, so you can insert a `` anywhere, and it will have the correct size. Try it in the sandbox above! **Context lets you write components that "adapt to their surroundings" and display themselves differently depending on _where_ (or, in other words, _in which context_) they are being rendered.** How context works might remind you of [CSS property inheritance.](https://developer.mozilla.org/en-US/docs/Web/CSS/inheritance) In CSS, you can specify `color: blue` for a `
    `, and any DOM node inside of it, no matter how deep, will inherit that color unless some other DOM node in the middle overrides it with `color: green`. Similarly, in React, the only way to override some context coming from above is to wrap children into a context provider with a different value. In CSS, different properties like `color` and `background-color` don't override each other. You can set all `
    `'s `color` to red without impacting `background-color`. Similarly, **different React contexts don't override each other.** Each context that you make with `createContext()` is completely separate from other ones, and ties together components using and providing *that particular* context. One component may use or provide many different contexts without a problem. ## Before you use context {/*before-you-use-context*/} Context is very tempting to use! However, this also means it's too easy to overuse it. **Just because you need to pass some props several levels deep doesn't mean you should put that information into context.** Here's a few alternatives you should consider before using context: 1. **Start by [passing props.](/learn/passing-props-to-a-component)** If your components are not trivial, it's not unusual to pass a dozen props down through a dozen components. It may feel like a slog, but it makes it very clear which components use which data! The person maintaining your code will be glad you've made the data flow explicit with props. 2. **Extract components and [pass JSX as `children`](/learn/passing-props-to-a-component#passing-jsx-as-children) to them.** If you pass some data through many layers of intermediate components that don't use that data (and only pass it further down), this often means that you forgot to extract some components along the way. For example, maybe you pass data props like `posts` to visual components that don't use them directly, like ``. Instead, make `Layout` take `children` as a prop, and render ``. This reduces the number of layers between the component specifying the data and the one that needs it. If neither of these approaches works well for you, consider context. ## Use cases for context {/*use-cases-for-context*/} * **Theming:** If your app lets the user change its appearance (e.g. dark mode), you can put a context provider at the top of your app, and use that context in components that need to adjust their visual look. * **Current account:** Many components might need to know the currently logged in user. Putting it in context makes it convenient to read it anywhere in the tree. Some apps also let you operate multiple accounts at the same time (e.g. to leave a comment as a different user). In those cases, it can be convenient to wrap a part of the UI into a nested provider with a different current account value. * **Routing:** Most routing solutions use context internally to hold the current route. This is how every link "knows" whether it's active or not. If you build your own router, you might want to do it too. * **Managing state:** As your app grows, you might end up with a lot of state closer to the top of your app. Many distant components below may want to change it. It is common to [use a reducer together with context](/learn/scaling-up-with-reducer-and-context) to manage complex state and pass it down to distant components without too much hassle. Context is not limited to static values. If you pass a different value on the next render, React will update all the components reading it below! This is why context is often used in combination with state. In general, if some information is needed by distant components in different parts of the tree, it's a good indication that context will help you. * Context lets a component provide some information to the entire tree below it. * To pass context: 1. Create and export it with `export const MyContext = createContext(defaultValue)`. 2. Pass it to the `useContext(MyContext)` Hook to read it in any child component, no matter how deep. 3. Wrap children into `` to provide it from a parent. * Context passes through any components in the middle. * Context lets you write components that "adapt to their surroundings". * Before you use context, try passing props or passing JSX as `children`. #### Replace prop drilling with context {/*replace-prop-drilling-with-context*/} In this example, toggling the checkbox changes the `imageSize` prop passed to each ``. The checkbox state is held in the top-level `App` component, but each `` needs to be aware of it. Currently, `App` passes `imageSize` to `List`, which passes it to each `Place`, which passes it to the `PlaceImage`. Remove the `imageSize` prop, and instead pass it from the `App` component directly to `PlaceImage`. You can declare context in `Context.js`. ```js src/App.js import { useState } from 'react'; import { places } from './data.js'; import { getImageUrl } from './utils.js'; export default function App() { const [isLarge, setIsLarge] = useState(false); const imageSize = isLarge ? 150 : 100; return ( <>
    ) } function List({ imageSize }) { const listItems = places.map(place =>
  • ); return
      {listItems}
    ; } function Place({ place, imageSize }) { return ( <>

    {place.name} {': ' + place.description}

    ); } function PlaceImage({ place, imageSize }) { return ( {place.name} ); } ``` ```js src/Context.js ``` ```js src/data.js export const places = [{ id: 0, name: 'Bo-Kaap in Cape Town, South Africa', description: 'The tradition of choosing bright colors for houses began in the late 20th century.', imageId: 'K9HVAGH' }, { id: 1, name: 'Rainbow Village in Taichung, Taiwan', description: 'To save the houses from demolition, Huang Yung-Fu, a local resident, painted all 1,200 of them in 1924.', imageId: '9EAYZrt' }, { id: 2, name: 'Macromural de Pachuca, Mexico', description: 'One of the largest murals in the world covering homes in a hillside neighborhood.', imageId: 'DgXHVwu' }, { id: 3, name: 'Selarón Staircase in Rio de Janeiro, Brazil', description: 'This landmark was created by Jorge Selarón, a Chilean-born artist, as a "tribute to the Brazilian people."', imageId: 'aeO3rpI' }, { id: 4, name: 'Burano, Italy', description: 'The houses are painted following a specific color system dating back to 16th century.', imageId: 'kxsph5C' }, { id: 5, name: 'Chefchaouen, Marocco', description: 'There are a few theories on why the houses are painted blue, including that the color repels mosquitos or that it symbolizes sky and heaven.', imageId: 'rTqKo46' }, { id: 6, name: 'Gamcheon Culture Village in Busan, South Korea', description: 'In 2009, the village was converted into a cultural hub by painting the houses and featuring exhibitions and art installations.', imageId: 'ZfQOOzf' }]; ``` ```js src/utils.js export function getImageUrl(place) { return ( 'https://i.imgur.com/' + place.imageId + 'l.jpg' ); } ``` ```css ul { list-style-type: none; padding: 0px 10px; } li { margin-bottom: 10px; display: grid; grid-template-columns: auto 1fr; gap: 20px; align-items: center; } ```
    Remove `imageSize` prop from all the components. Create and export `ImageSizeContext` from `Context.js`. Then wrap the List into `` to pass the value down, and `useContext(ImageSizeContext)` to read it in the `PlaceImage`: ```js src/App.js import { useState, useContext } from 'react'; import { places } from './data.js'; import { getImageUrl } from './utils.js'; import { ImageSizeContext } from './Context.js'; export default function App() { const [isLarge, setIsLarge] = useState(false); const imageSize = isLarge ? 150 : 100; return (
    ) } function List() { const listItems = places.map(place =>
  • ); return
      {listItems}
    ; } function Place({ place }) { return ( <>

    {place.name} {': ' + place.description}

    ); } function PlaceImage({ place }) { const imageSize = useContext(ImageSizeContext); return ( {place.name} ); } ``` ```js src/Context.js import { createContext } from 'react'; export const ImageSizeContext = createContext(500); ``` ```js src/data.js export const places = [{ id: 0, name: 'Bo-Kaap in Cape Town, South Africa', description: 'The tradition of choosing bright colors for houses began in the late 20th century.', imageId: 'K9HVAGH' }, { id: 1, name: 'Rainbow Village in Taichung, Taiwan', description: 'To save the houses from demolition, Huang Yung-Fu, a local resident, painted all 1,200 of them in 1924.', imageId: '9EAYZrt' }, { id: 2, name: 'Macromural de Pachuca, Mexico', description: 'One of the largest murals in the world covering homes in a hillside neighborhood.', imageId: 'DgXHVwu' }, { id: 3, name: 'Selarón Staircase in Rio de Janeiro, Brazil', description: 'This landmark was created by Jorge Selarón, a Chilean-born artist, as a "tribute to the Brazilian people".', imageId: 'aeO3rpI' }, { id: 4, name: 'Burano, Italy', description: 'The houses are painted following a specific color system dating back to 16th century.', imageId: 'kxsph5C' }, { id: 5, name: 'Chefchaouen, Marocco', description: 'There are a few theories on why the houses are painted blue, including that the color repels mosquitos or that it symbolizes sky and heaven.', imageId: 'rTqKo46' }, { id: 6, name: 'Gamcheon Culture Village in Busan, South Korea', description: 'In 2009, the village was converted into a cultural hub by painting the houses and featuring exhibitions and art installations.', imageId: 'ZfQOOzf' }]; ``` ```js src/utils.js export function getImageUrl(place) { return ( 'https://i.imgur.com/' + place.imageId + 'l.jpg' ); } ``` ```css ul { list-style-type: none; padding: 0px 10px; } li { margin-bottom: 10px; display: grid; grid-template-columns: auto 1fr; gap: 20px; align-items: center; } ```
    Note how components in the middle don't need to pass `imageSize` anymore.
    \ No newline at end of file +--- title: 使用Context深層傳遞參數 --- 大多數情況下,你會通過 props 將信息從父組件傳遞給子組件。但是,如果你必須通過許多中間組件向下傳遞 props,或者是在你應用的許多組件中需要傳遞相同的信息,傳遞props會變得冗長和不便。 *Context* 不需要通過 props 顯示傳遞,它允許父組件向其下層的無論多深的任意組件傳遞信息, - 什麼是「prop 逐級傳遞」 - 如何使用 context 替代重複的參數傳遞 - Context 的常見用法 - Context 的常用代替方案 ## 傳遞 props 帶來的問題 {/*the-problem-with-passing-props*/} [傳遞 props](/learn/passing-props-to-a-component) 是一種將數據通過 UI 樹顯式傳遞到使用它的組件的好方法。 但是當你需要在組件樹中深層傳遞參數以及需要在組件間複用相同的參數時,傳遞 props 就會變得冗長且不便。最近的根節點父組件可能離需要數據的組件很遠,[狀態提升](/learn/sharing-state-between-components)至過高的層級會導致「prop 逐級傳遞」的情況。 狀態提升 Prop 逐級傳遞 要是有一種方法能夠在組件樹中「直傳」數據到所需的組件還不用通過 props,可就太好了。React 的 context 功能可以做到。 ## Context: 傳遞 props 的另一種方法 {/*context-an-alternative-to-passing-props*/} Context 讓父組件可以為它下面的整個組件樹提供數據。Context 有很多種用途。這有一個示例。思考一下這個 `Heading` 組件接收一個 `level` 參數決定它標題尺寸的場景: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title Heading Sub-heading Sub-sub-heading Sub-sub-sub-heading Sub-sub-sub-sub-heading
    ); } ``` ```js src/Section.js export default function Section({ children }) { return (
    {children}
    ); } ``` ```js src/Heading.js export default function Heading({ level, children }) { switch (level) { case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    假如你想讓相同 `Section` 中的多個 Heading 總是有相同的尺寸: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title
    Heading Heading Heading
    Sub-heading Sub-heading Sub-heading
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ); } ``` ```js src/Section.js export default function Section({ children }) { return (
    {children}
    ); } ``` ```js src/Heading.js export default function Heading({ level, children }) { switch (level) { case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    現在,你將 `level` 參數分別傳遞給每個 ``: ```js
    About Photos Videos
    ``` 將 `level` 參數傳遞給 `
    ` 組件而不是傳給 `` 組件,會看起來更好一些。這樣的話你可以強制使同一個 section 中的所有標題都有相同的尺寸: ```js
    About Photos Videos
    ``` 但是 `` 組件是如何知道離它最近的 `
    ` 的 level 的呢?**這需要子組件可以通過某種方式「訪問」到組件樹中某處在其上層的數據。** 你不能只通過 props 來實現它。這就是 context 大展身手的地方。你可以通過以下三個步驟實現它: 1. **創建** 一個 context。 (你可以呼叫它為 `LevelContext`, 因為它表示的是標題級別。) 2. 在需要數據的組件內 **使用** 剛剛創建的 context。(`Heading` 將會使用 `LevelContext`。) 3. 在指定數據的組件中 **提供** 這個 context(`Section` 將會提供 `LevelContext`。) Context 可以讓父節點,甚至是很遠的父節點都可以為其內部的整個組件樹提供數據。 同級子組件使用 context 遠親組件使用 context ### Step 1: 創建 context {/*step-1-create-the-context*/} 首先, 你需要創建這個 context. 你需要將其 **從一個文件中導出** 來讓你的組件可以使用它: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title
    Heading Heading Heading
    Sub-heading Sub-heading Sub-heading
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ); } ``` ```js src/Section.js export default function Section({ children }) { return (
    {children}
    ); } ``` ```js src/Heading.js export default function Heading({ level, children }) { switch (level) { case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```js src/LevelContext.js active import { createContext } from 'react'; export const LevelContext = createContext(1); ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    `createContext` 只需要 _default_ value。 這裡,`1` 代表最大的標題級別,但是你可以傳遞任何類型的 value(甚至是一個對象)。你將在下一個步驟中見識到 default value 的意義。 ### Step 2: Use the context {/*step-2-use-the-context*/} 從 React 中引入 `useContext` Hook 以及你剛剛創建的 context: ```js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; ``` 現在,`Heading` 組件從 props 中讀取 `level`: ```js export default function Heading({ level, children }) { // ... } ``` 然後,刪除 `level` 參數並從你剛剛引入的 `LevelContext` 中讀取值: ```js {2} export default function Heading({ children }) { const level = useContext(LevelContext); // ... } ``` `useContext` 是一個 Hook。 就像 `useState` 和 `useReducer` 一樣, 你只能在 React 組件中(不是循環或者條件裡)立即調用 Hook。 **`useContext` 告訴 React `Heading` 組件想要讀取 `LevelContext`。** 現在 `Heading` 組件中沒有 `level` 參數, 你不再需要像這樣在你的 JSX 中將 level 參數傳遞給 `Heading`: ```js
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ``` 更新一下 JSX 來讓 `Section` 組件可以接收 level 參數: ```jsx
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ``` 提醒一下,這是你使得代碼能正常運行的必備步驟: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title
    Heading Heading Heading
    Sub-heading Sub-heading Sub-heading
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ); } ``` ```js src/Section.js export default function Section({ children }) { return (
    {children}
    ); } ``` ```js src/Heading.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Heading({ children }) { const level = useContext(LevelContext); switch (level) { case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```js src/LevelContext.js import { createContext } from 'react'; export const LevelContext = createContext(1); ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    注意,這個示例還不能運行!所有的 headings 的尺寸都一樣,因為 **哪怕你正在 *使用* context, 你也還沒 *提供* 它。** React 不知道從哪獲取這個 context! 如果你不提供 context, React 將會使用你在上一步指定的默認值。在這個例子中,你將參數 `1` 傳遞給了 `createContext`,因此 `useContext(LevelContext)` 會返回 `1`,同時把所有的標題都設置為 `

    `。我们可以通过让每个 `Section` 提供它自己的 context 来修复这个问题。 ### Step 3: 提供 context {/*step-3-provide-the-context*/} `Section` 組件目前渲染傳入它的子組件: ```js export default function Section({ children }) { return (
    {children}
    ); } ``` **用一個 context provider 把它们包裹起来** 便可以提供 `LevelContext` 給它們: ```js {1,6,8} import { LevelContext } from './LevelContext.js'; export default function Section({ level, children }) { return (
    {children}
    ); } ``` 這會告訴 React:「如果在 `
    ` 組件中的任何子組件請求 `LevelContext`,給它們這個 `level`。」組件會使用 UI 樹中在它上層最近的那個 `` 傳遞過來的值。 ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title
    Heading Heading Heading
    Sub-heading Sub-heading Sub-heading
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ); } ``` ```js src/Section.js import { LevelContext } from './LevelContext.js'; export default function Section({ level, children }) { return (
    {children}
    ); } ``` ```js src/Heading.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Heading({ children }) { const level = useContext(LevelContext); switch (level) { case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```js src/LevelContext.js import { createContext } from 'react'; export const LevelContext = createContext(1); ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    這與原始代碼的運行結果是相同的,但是你不需要傳遞 `level` 參數給每一個 `Heading` 組件了!取而代之, 它通過訪問上層最近的 `Section` 來「斷定」它的標題級別: 1. 你將一個 `level` 參數傳給 `
    `。 2. `Section` 把它的子元素包裹在 `` 中。 3. `Heading` 使用 `useContext(LevelContext)` 訪問上層最近的 `LevelContext` 提供的值。 ## 在相同的组件中使用并提供 context {/*using-and-providing-context-from-the-same-component*/} 現在,你仍需要手動指定每個 section 的 `level`: ```js export default function Page() { return (
    ...
    ...
    ... ``` 由於 context 讓你可以從上層的組件中讀取信息,每個 `Section` 都會從上層的 `Section` 讀取 `level`,並自動向下層傳遞 `level + 1`。你可以像下面這樣做。 ```js src/Section.js {5,8} import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Section({ children }) { const level = useContext(LevelContext); return (
    {children}
    ); } ``` 這樣修改後,你不需要將 `level` 參數傳給 `
    ` *或者是* `` 了: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title
    Heading Heading Heading
    Sub-heading Sub-heading Sub-heading
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ); } ``` ```js src/Section.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Section({ children }) { const level = useContext(LevelContext); return (
    {children}
    ); } ``` ```js src/Heading.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Heading({ children }) { const level = useContext(LevelContext); switch (level) { case 0: throw Error('Heading must be inside a Section!'); case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```js src/LevelContext.js import { createContext } from 'react'; export const LevelContext = createContext(0); ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    現在,`Heading` 和 `Section` 都通過讀取 `LevelContext` 來判斷它們「深度」。並且 `Section` 把它的子組件都包裹在 `LevelContext` 中來指定其中的任何內容都處於一個「更深」的級別。 該示例使用標題級別來說明,是因為它們直觀地顯示了嵌套組件是如何覆蓋 context。但是 context 對於許多其他場景也很有用。你可以用它來傳遞整個字數需要的任何信息: 當前的顏色主題、當前的登錄用戶等。 ## Context 能穿過中間層級的組件 {/*context-passes-through-intermediate-components*/} 你可以在提供 context 的組件和使用它的組件之間的層級插入任意數量的組件。這包括像 `
    ` 這樣的內置組件和你自己創建的組件。 該實例中,相同的 `Post` 組件(帶有虛線邊框)在兩個不同的嵌套層級上渲染。注意,它內部的 `` 會自動從最近的 `
    ` 獲取它的級別: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function ProfilePage() { return (
    My Profile
    ); } function AllPosts() { return (
    Posts
    ); } function RecentPosts() { return (
    Recent Posts
    ); } function Post({ title, body }) { return (
    {title}

    {body}

    ); } ``` ```js src/Section.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Section({ children, isFancy }) { const level = useContext(LevelContext); return (
    {children}
    ); } ``` ```js src/Heading.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Heading({ children }) { const level = useContext(LevelContext); switch (level) { case 0: throw Error('Heading must be inside a Section!'); case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```js src/LevelContext.js import { createContext } from 'react'; export const LevelContext = createContext(0); ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } .fancy { border: 4px dashed pink; } ```
    你不需要做任何特殊的操作。`Section` 為它內部的樹指定了一個 context,所以你可以在任何地方插入一個 ``,並且它會有正確的尺寸。在上邊的 sandbox 嘗試下! **Context 讓你可以編寫「適應周圍的環境」的組件,並且根據 _在哪_ (或者說,_在哪個 context 中_)來渲染它們不同的樣子。** Context 的工作方式可能會讓你想起[CSS 屬性繼承](https://developer.mozilla.org/en-US/docs/Web/CSS/inheritance) 在 CSS, 你可以指定 `color: blue` 給一個 `
    `, 並且在其中的任意 DOM 節點,無論多深,都會繼承那個顏色,除非中間的其他 DOM 節點用 `color: green` 覆蓋它。類似地,在 React 中,覆蓋來自上層的某些 Context 的唯一方法時間子組件包裹到一個提供不同值的 context provider 中。 在 CSS 中,不同屬性的 `color` 和 `background-color` 不會彼此覆蓋。你可以設置所有的 `
    ` 都為 `color` 紅色,還不會影響 `background-color`。類似地, **不同的 React context 不會彼此覆蓋。** 你用 `createContext()` 創建的每個 context 都和其他 context 完全分離,只有使用和提供 *那個特定的* context 才會聯繫到一起。一個組件可以毫無問題地使用或者是提供不同的 context。 ## 在你使用 context 之前 {/*before-you-use-context*/} 使用 Context 看起來非常誘人!然而,這也意味著它很容易被過度使用。 **若你只是想將一些 props 逐層傳遞多個層級,這並不意味著你需要把這些信息放到 context 中。** 在你使用 context 之前,這裡有一些可供你選擇的代替方案: 1. **從 [傳遞 props 開始。](/learn/passing-props-to-a-component)** 如果你的組件看起來不起眼,那麼通過十幾個組件向下傳遞一堆 props 並不少見。這有點像是在埋頭苦幹,但是這樣做可以讓哪些組件用了哪些數據變得十分清晰!維護你代碼的人會很高興你用 props 傳遞數據,這會讓數據流變得更加清晰。 2. **抽象組件並 [把 JSX 作為 `children`](/learn/passing-props-to-a-component#passing-jsx-as-children) 傳給它們。** 如果你通過很多層不使用該數據的中間組件(並且只會向下傳遞)來傳遞數據。舉個例子,你可能想把一些像 `posts` 的 props 數據傳遞到不會直接使用這個參數的組件,比如說 ``。更好的方式是,讓 `Layout` 把 `children` 當做一個參數,然後渲染 ``。這樣就減少了定義數據的組件和使用數據的組件之間的層級。 如果這兩種替代方案都不適合你,再考慮使用 context。 ## Context 的使用場景 {/*use-cases-for-context*/} * **主題:** 如果你的應用允許用戶更改其外觀(例如 dark mode),你可以在應用頂層定義一個 context provider,並在需要調整其外觀的組件中使用該 context。 * **當前賬戶:** 許多組件可能需要知道當前登錄的用戶信息。把它放到 context 中可以方便在樹中任何為止讀取它。某些應用還允許你同時操作多個賬戶(例如,以不同用戶的身份發表評論)。在這些情況下,將 UI 的一部分包裹到具有不同賬戶數據的 provider 中會很方便。 * **路由:** 大多數路由解決方案在其內部使用 context 來保存當前路由。這就是每個鏈接「知道」它是否處於活動狀態的方式。如果你創建自己的路由,你可能也會這麼做。 * **狀態管理:** 隨著你的應用的開發,最終在靠近應用頂部的為止可能會有很多 state。許多遙遠的下層組件可能想要修改它們。通常 [將 reducer 與 context 搭配使用](/learn/scaling-up-with-reducer-and-context) 來管理複雜的狀態並將其傳遞給深層的組件來避免過多的麻煩。 Context 不局限於靜態值。如果你在下一次渲染時傳遞不同的值,React 將會更新所有讀取它的下層組件!這就是 context 經常和 state 結合使用的原因。 通常來說,如果樹中不同部分的遠距離組件需要傳遞某些信息,使用 context 是一個很好的選擇。 * Context 讓一個組件向其下整個樹的組件提供信息。 * 傳遞 context 的步驟: 1. 通過 `export const MyContext = createContext(defaultValue)` 創建並導出 context。 2. 在無論層級多深的任何子組件中,把 context 傳遞給 `useContext(MyContext)` Hook 來讀取它。 3. 在父組件中把 children 包裹在 `` 中來提供 context。 * Context 會穿過在中間的所有組件。 * Context 讓你寫出「適應周圍環境的」組件。 * 在你使用 context 之前,先嘗試下傳遞 props 或者將 JSX 作為 `children` 傳遞。 #### 用 context 代替 prop 逐級傳遞 {/*replace-prop-drilling-with-context*/} 該示例中,切換復選框狀態會修改傳入的每個 `` 的 `imageSize` 參數。復選框的 state 保存在頂層的 `App` 組件中,但每個 `` 都需要注意它。 现在, `App` 将 `imageSize` 傳遞給 `List`,再將其傳遞給每個 `Place`,`Place` 又將其傳遞給 `PlaceImage`。刪除 `imageSize` 參數,然後在 `App` 組件中直接將其傳遞給 `PlaceImage`。 你可以在 `Context.js` 中聲明 context。 ```js src/App.js import { useState } from 'react'; import { places } from './data.js'; import { getImageUrl } from './utils.js'; export default function App() { const [isLarge, setIsLarge] = useState(false); const imageSize = isLarge ? 150 : 100; return ( <>
    ) } function List({ imageSize }) { const listItems = places.map(place =>
  • ); return
      {listItems}
    ; } function Place({ place, imageSize }) { return ( <>

    {place.name} {': ' + place.description}

    ); } function PlaceImage({ place, imageSize }) { return ( {place.name} ); } ``` ```js src/Context.js ``` ```js src/data.js export const places = [{ id: 0, name: 'Bo-Kaap in Cape Town, South Africa', description: 'The tradition of choosing bright colors for houses began in the late 20th century.', imageId: 'K9HVAGH' }, { id: 1, name: 'Rainbow Village in Taichung, Taiwan', description: 'To save the houses from demolition, Huang Yung-Fu, a local resident, painted all 1,200 of them in 1924.', imageId: '9EAYZrt' }, { id: 2, name: 'Macromural de Pachuca, Mexico', description: 'One of the largest murals in the world covering homes in a hillside neighborhood.', imageId: 'DgXHVwu' }, { id: 3, name: 'Selarón Staircase in Rio de Janeiro, Brazil', description: 'This landmark was created by Jorge Selarón, a Chilean-born artist, as a "tribute to the Brazilian people."', imageId: 'aeO3rpI' }, { id: 4, name: 'Burano, Italy', description: 'The houses are painted following a specific color system dating back to 16th century.', imageId: 'kxsph5C' }, { id: 5, name: 'Chefchaouen, Marocco', description: 'There are a few theories on why the houses are painted blue, including that the color repels mosquitos or that it symbolizes sky and heaven.', imageId: 'rTqKo46' }, { id: 6, name: 'Gamcheon Culture Village in Busan, South Korea', description: 'In 2009, the village was converted into a cultural hub by painting the houses and featuring exhibitions and art installations.', imageId: 'ZfQOOzf' }]; ``` ```js src/utils.js export function getImageUrl(place) { return ( 'https://i.imgur.com/' + place.imageId + 'l.jpg' ); } ``` ```css ul { list-style-type: none; padding: 0px 10px; } li { margin-bottom: 10px; display: grid; grid-template-columns: auto 1fr; gap: 20px; align-items: center; } ```
    Remove `imageSize` prop from all the components. Create and export `ImageSizeContext` from `Context.js`. Then wrap the List into `` to pass the value down, and `useContext(ImageSizeContext)` to read it in the `PlaceImage`: ```js src/App.js import { useState, useContext } from 'react'; import { places } from './data.js'; import { getImageUrl } from './utils.js'; import { ImageSizeContext } from './Context.js'; export default function App() { const [isLarge, setIsLarge] = useState(false); const imageSize = isLarge ? 150 : 100; return (
    ) } function List() { const listItems = places.map(place =>
  • ); return
      {listItems}
    ; } function Place({ place }) { return ( <>

    {place.name} {': ' + place.description}

    ); } function PlaceImage({ place }) { const imageSize = useContext(ImageSizeContext); return ( {place.name} ); } ``` ```js src/Context.js import { createContext } from 'react'; export const ImageSizeContext = createContext(500); ``` ```js src/data.js export const places = [{ id: 0, name: 'Bo-Kaap in Cape Town, South Africa', description: 'The tradition of choosing bright colors for houses began in the late 20th century.', imageId: 'K9HVAGH' }, { id: 1, name: 'Rainbow Village in Taichung, Taiwan', description: 'To save the houses from demolition, Huang Yung-Fu, a local resident, painted all 1,200 of them in 1924.', imageId: '9EAYZrt' }, { id: 2, name: 'Macromural de Pachuca, Mexico', description: 'One of the largest murals in the world covering homes in a hillside neighborhood.', imageId: 'DgXHVwu' }, { id: 3, name: 'Selarón Staircase in Rio de Janeiro, Brazil', description: 'This landmark was created by Jorge Selarón, a Chilean-born artist, as a "tribute to the Brazilian people".', imageId: 'aeO3rpI' }, { id: 4, name: 'Burano, Italy', description: 'The houses are painted following a specific color system dating back to 16th century.', imageId: 'kxsph5C' }, { id: 5, name: 'Chefchaouen, Marocco', description: 'There are a few theories on why the houses are painted blue, including that the color repels mosquitos or that it symbolizes sky and heaven.', imageId: 'rTqKo46' }, { id: 6, name: 'Gamcheon Culture Village in Busan, South Korea', description: 'In 2009, the village was converted into a cultural hub by painting the houses and featuring exhibitions and art installations.', imageId: 'ZfQOOzf' }]; ``` ```js src/utils.js export function getImageUrl(place) { return ( 'https://i.imgur.com/' + place.imageId + 'l.jpg' ); } ``` ```css ul { list-style-type: none; padding: 0px 10px; } li { margin-bottom: 10px; display: grid; grid-template-columns: auto 1fr; gap: 20px; align-items: center; } ```
    注意,中間的組件不再需要傳遞 `imageSize`。
    \ No newline at end of file From 6f2c3833e0cfaace694172c0bda6f1ac5d778ffb Mon Sep 17 00:00:00 2001 From: ShenHdou <15574380273@163.com> Date: Tue, 23 Apr 2024 00:05:31 +0800 Subject: [PATCH 3/5] docs: translate src/content/learn/passing-data-deeply-with-context.md --- .../learn/passing-data-deeply-with-context.md | 1166 ++++++++++++++++- 1 file changed, 1165 insertions(+), 1 deletion(-) diff --git a/src/content/learn/passing-data-deeply-with-context.md b/src/content/learn/passing-data-deeply-with-context.md index 36cceb6b5..43291528c 100644 --- a/src/content/learn/passing-data-deeply-with-context.md +++ b/src/content/learn/passing-data-deeply-with-context.md @@ -1 +1,1165 @@ ---- title: 使用Context深層傳遞參數 --- 大多數情況下,你會通過 props 將信息從父組件傳遞給子組件。但是,如果你必須通過許多中間組件向下傳遞 props,或者是在你應用的許多組件中需要傳遞相同的信息,傳遞props會變得冗長和不便。 *Context* 不需要通過 props 顯示傳遞,它允許父組件向其下層的無論多深的任意組件傳遞信息, - 什麼是「prop 逐級傳遞」 - 如何使用 context 替代重複的參數傳遞 - Context 的常見用法 - Context 的常用代替方案 ## 傳遞 props 帶來的問題 {/*the-problem-with-passing-props*/} [傳遞 props](/learn/passing-props-to-a-component) 是一種將數據通過 UI 樹顯式傳遞到使用它的組件的好方法。 但是當你需要在組件樹中深層傳遞參數以及需要在組件間複用相同的參數時,傳遞 props 就會變得冗長且不便。最近的根節點父組件可能離需要數據的組件很遠,[狀態提升](/learn/sharing-state-between-components)至過高的層級會導致「prop 逐級傳遞」的情況。 狀態提升 Prop 逐級傳遞 要是有一種方法能夠在組件樹中「直傳」數據到所需的組件還不用通過 props,可就太好了。React 的 context 功能可以做到。 ## Context: 傳遞 props 的另一種方法 {/*context-an-alternative-to-passing-props*/} Context 讓父組件可以為它下面的整個組件樹提供數據。Context 有很多種用途。這有一個示例。思考一下這個 `Heading` 組件接收一個 `level` 參數決定它標題尺寸的場景: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title Heading Sub-heading Sub-sub-heading Sub-sub-sub-heading Sub-sub-sub-sub-heading
    ); } ``` ```js src/Section.js export default function Section({ children }) { return (
    {children}
    ); } ``` ```js src/Heading.js export default function Heading({ level, children }) { switch (level) { case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    假如你想讓相同 `Section` 中的多個 Heading 總是有相同的尺寸: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title
    Heading Heading Heading
    Sub-heading Sub-heading Sub-heading
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ); } ``` ```js src/Section.js export default function Section({ children }) { return (
    {children}
    ); } ``` ```js src/Heading.js export default function Heading({ level, children }) { switch (level) { case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    現在,你將 `level` 參數分別傳遞給每個 ``: ```js
    About Photos Videos
    ``` 將 `level` 參數傳遞給 `
    ` 組件而不是傳給 `` 組件,會看起來更好一些。這樣的話你可以強制使同一個 section 中的所有標題都有相同的尺寸: ```js
    About Photos Videos
    ``` 但是 `` 組件是如何知道離它最近的 `
    ` 的 level 的呢?**這需要子組件可以通過某種方式「訪問」到組件樹中某處在其上層的數據。** 你不能只通過 props 來實現它。這就是 context 大展身手的地方。你可以通過以下三個步驟實現它: 1. **創建** 一個 context。 (你可以呼叫它為 `LevelContext`, 因為它表示的是標題級別。) 2. 在需要數據的組件內 **使用** 剛剛創建的 context。(`Heading` 將會使用 `LevelContext`。) 3. 在指定數據的組件中 **提供** 這個 context(`Section` 將會提供 `LevelContext`。) Context 可以讓父節點,甚至是很遠的父節點都可以為其內部的整個組件樹提供數據。 同級子組件使用 context 遠親組件使用 context ### Step 1: 創建 context {/*step-1-create-the-context*/} 首先, 你需要創建這個 context. 你需要將其 **從一個文件中導出** 來讓你的組件可以使用它: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title
    Heading Heading Heading
    Sub-heading Sub-heading Sub-heading
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ); } ``` ```js src/Section.js export default function Section({ children }) { return (
    {children}
    ); } ``` ```js src/Heading.js export default function Heading({ level, children }) { switch (level) { case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```js src/LevelContext.js active import { createContext } from 'react'; export const LevelContext = createContext(1); ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    `createContext` 只需要 _default_ value。 這裡,`1` 代表最大的標題級別,但是你可以傳遞任何類型的 value(甚至是一個對象)。你將在下一個步驟中見識到 default value 的意義。 ### Step 2: Use the context {/*step-2-use-the-context*/} 從 React 中引入 `useContext` Hook 以及你剛剛創建的 context: ```js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; ``` 現在,`Heading` 組件從 props 中讀取 `level`: ```js export default function Heading({ level, children }) { // ... } ``` 然後,刪除 `level` 參數並從你剛剛引入的 `LevelContext` 中讀取值: ```js {2} export default function Heading({ children }) { const level = useContext(LevelContext); // ... } ``` `useContext` 是一個 Hook。 就像 `useState` 和 `useReducer` 一樣, 你只能在 React 組件中(不是循環或者條件裡)立即調用 Hook。 **`useContext` 告訴 React `Heading` 組件想要讀取 `LevelContext`。** 現在 `Heading` 組件中沒有 `level` 參數, 你不再需要像這樣在你的 JSX 中將 level 參數傳遞給 `Heading`: ```js
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ``` 更新一下 JSX 來讓 `Section` 組件可以接收 level 參數: ```jsx
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ``` 提醒一下,這是你使得代碼能正常運行的必備步驟: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title
    Heading Heading Heading
    Sub-heading Sub-heading Sub-heading
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ); } ``` ```js src/Section.js export default function Section({ children }) { return (
    {children}
    ); } ``` ```js src/Heading.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Heading({ children }) { const level = useContext(LevelContext); switch (level) { case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```js src/LevelContext.js import { createContext } from 'react'; export const LevelContext = createContext(1); ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    注意,這個示例還不能運行!所有的 headings 的尺寸都一樣,因為 **哪怕你正在 *使用* context, 你也還沒 *提供* 它。** React 不知道從哪獲取這個 context! 如果你不提供 context, React 將會使用你在上一步指定的默認值。在這個例子中,你將參數 `1` 傳遞給了 `createContext`,因此 `useContext(LevelContext)` 會返回 `1`,同時把所有的標題都設置為 `

    `。我们可以通过让每个 `Section` 提供它自己的 context 来修复这个问题。 ### Step 3: 提供 context {/*step-3-provide-the-context*/} `Section` 組件目前渲染傳入它的子組件: ```js export default function Section({ children }) { return (
    {children}
    ); } ``` **用一個 context provider 把它们包裹起来** 便可以提供 `LevelContext` 給它們: ```js {1,6,8} import { LevelContext } from './LevelContext.js'; export default function Section({ level, children }) { return (
    {children}
    ); } ``` 這會告訴 React:「如果在 `
    ` 組件中的任何子組件請求 `LevelContext`,給它們這個 `level`。」組件會使用 UI 樹中在它上層最近的那個 `` 傳遞過來的值。 ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title
    Heading Heading Heading
    Sub-heading Sub-heading Sub-heading
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ); } ``` ```js src/Section.js import { LevelContext } from './LevelContext.js'; export default function Section({ level, children }) { return (
    {children}
    ); } ``` ```js src/Heading.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Heading({ children }) { const level = useContext(LevelContext); switch (level) { case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```js src/LevelContext.js import { createContext } from 'react'; export const LevelContext = createContext(1); ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    這與原始代碼的運行結果是相同的,但是你不需要傳遞 `level` 參數給每一個 `Heading` 組件了!取而代之, 它通過訪問上層最近的 `Section` 來「斷定」它的標題級別: 1. 你將一個 `level` 參數傳給 `
    `。 2. `Section` 把它的子元素包裹在 `` 中。 3. `Heading` 使用 `useContext(LevelContext)` 訪問上層最近的 `LevelContext` 提供的值。 ## 在相同的组件中使用并提供 context {/*using-and-providing-context-from-the-same-component*/} 現在,你仍需要手動指定每個 section 的 `level`: ```js export default function Page() { return (
    ...
    ...
    ... ``` 由於 context 讓你可以從上層的組件中讀取信息,每個 `Section` 都會從上層的 `Section` 讀取 `level`,並自動向下層傳遞 `level + 1`。你可以像下面這樣做。 ```js src/Section.js {5,8} import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Section({ children }) { const level = useContext(LevelContext); return (
    {children}
    ); } ``` 這樣修改後,你不需要將 `level` 參數傳給 `
    ` *或者是* `` 了: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return (
    Title
    Heading Heading Heading
    Sub-heading Sub-heading Sub-heading
    Sub-sub-heading Sub-sub-heading Sub-sub-heading
    ); } ``` ```js src/Section.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Section({ children }) { const level = useContext(LevelContext); return (
    {children}
    ); } ``` ```js src/Heading.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Heading({ children }) { const level = useContext(LevelContext); switch (level) { case 0: throw Error('Heading must be inside a Section!'); case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```js src/LevelContext.js import { createContext } from 'react'; export const LevelContext = createContext(0); ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } ```
    現在,`Heading` 和 `Section` 都通過讀取 `LevelContext` 來判斷它們「深度」。並且 `Section` 把它的子組件都包裹在 `LevelContext` 中來指定其中的任何內容都處於一個「更深」的級別。 該示例使用標題級別來說明,是因為它們直觀地顯示了嵌套組件是如何覆蓋 context。但是 context 對於許多其他場景也很有用。你可以用它來傳遞整個字數需要的任何信息: 當前的顏色主題、當前的登錄用戶等。 ## Context 能穿過中間層級的組件 {/*context-passes-through-intermediate-components*/} 你可以在提供 context 的組件和使用它的組件之間的層級插入任意數量的組件。這包括像 `
    ` 這樣的內置組件和你自己創建的組件。 該實例中,相同的 `Post` 組件(帶有虛線邊框)在兩個不同的嵌套層級上渲染。注意,它內部的 `` 會自動從最近的 `
    ` 獲取它的級別: ```js import Heading from './Heading.js'; import Section from './Section.js'; export default function ProfilePage() { return (
    My Profile
    ); } function AllPosts() { return (
    Posts
    ); } function RecentPosts() { return (
    Recent Posts
    ); } function Post({ title, body }) { return (
    {title}

    {body}

    ); } ``` ```js src/Section.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Section({ children, isFancy }) { const level = useContext(LevelContext); return (
    {children}
    ); } ``` ```js src/Heading.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; export default function Heading({ children }) { const level = useContext(LevelContext); switch (level) { case 0: throw Error('Heading must be inside a Section!'); case 1: return

    {children}

    ; case 2: return

    {children}

    ; case 3: return

    {children}

    ; case 4: return

    {children}

    ; case 5: return
    {children}
    ; case 6: return
    {children}
    ; default: throw Error('Unknown level: ' + level); } } ``` ```js src/LevelContext.js import { createContext } from 'react'; export const LevelContext = createContext(0); ``` ```css .section { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #aaa; } .fancy { border: 4px dashed pink; } ```
    你不需要做任何特殊的操作。`Section` 為它內部的樹指定了一個 context,所以你可以在任何地方插入一個 ``,並且它會有正確的尺寸。在上邊的 sandbox 嘗試下! **Context 讓你可以編寫「適應周圍的環境」的組件,並且根據 _在哪_ (或者說,_在哪個 context 中_)來渲染它們不同的樣子。** Context 的工作方式可能會讓你想起[CSS 屬性繼承](https://developer.mozilla.org/en-US/docs/Web/CSS/inheritance) 在 CSS, 你可以指定 `color: blue` 給一個 `
    `, 並且在其中的任意 DOM 節點,無論多深,都會繼承那個顏色,除非中間的其他 DOM 節點用 `color: green` 覆蓋它。類似地,在 React 中,覆蓋來自上層的某些 Context 的唯一方法時間子組件包裹到一個提供不同值的 context provider 中。 在 CSS 中,不同屬性的 `color` 和 `background-color` 不會彼此覆蓋。你可以設置所有的 `
    ` 都為 `color` 紅色,還不會影響 `background-color`。類似地, **不同的 React context 不會彼此覆蓋。** 你用 `createContext()` 創建的每個 context 都和其他 context 完全分離,只有使用和提供 *那個特定的* context 才會聯繫到一起。一個組件可以毫無問題地使用或者是提供不同的 context。 ## 在你使用 context 之前 {/*before-you-use-context*/} 使用 Context 看起來非常誘人!然而,這也意味著它很容易被過度使用。 **若你只是想將一些 props 逐層傳遞多個層級,這並不意味著你需要把這些信息放到 context 中。** 在你使用 context 之前,這裡有一些可供你選擇的代替方案: 1. **從 [傳遞 props 開始。](/learn/passing-props-to-a-component)** 如果你的組件看起來不起眼,那麼通過十幾個組件向下傳遞一堆 props 並不少見。這有點像是在埋頭苦幹,但是這樣做可以讓哪些組件用了哪些數據變得十分清晰!維護你代碼的人會很高興你用 props 傳遞數據,這會讓數據流變得更加清晰。 2. **抽象組件並 [把 JSX 作為 `children`](/learn/passing-props-to-a-component#passing-jsx-as-children) 傳給它們。** 如果你通過很多層不使用該數據的中間組件(並且只會向下傳遞)來傳遞數據。舉個例子,你可能想把一些像 `posts` 的 props 數據傳遞到不會直接使用這個參數的組件,比如說 ``。更好的方式是,讓 `Layout` 把 `children` 當做一個參數,然後渲染 ``。這樣就減少了定義數據的組件和使用數據的組件之間的層級。 如果這兩種替代方案都不適合你,再考慮使用 context。 ## Context 的使用場景 {/*use-cases-for-context*/} * **主題:** 如果你的應用允許用戶更改其外觀(例如 dark mode),你可以在應用頂層定義一個 context provider,並在需要調整其外觀的組件中使用該 context。 * **當前賬戶:** 許多組件可能需要知道當前登錄的用戶信息。把它放到 context 中可以方便在樹中任何為止讀取它。某些應用還允許你同時操作多個賬戶(例如,以不同用戶的身份發表評論)。在這些情況下,將 UI 的一部分包裹到具有不同賬戶數據的 provider 中會很方便。 * **路由:** 大多數路由解決方案在其內部使用 context 來保存當前路由。這就是每個鏈接「知道」它是否處於活動狀態的方式。如果你創建自己的路由,你可能也會這麼做。 * **狀態管理:** 隨著你的應用的開發,最終在靠近應用頂部的為止可能會有很多 state。許多遙遠的下層組件可能想要修改它們。通常 [將 reducer 與 context 搭配使用](/learn/scaling-up-with-reducer-and-context) 來管理複雜的狀態並將其傳遞給深層的組件來避免過多的麻煩。 Context 不局限於靜態值。如果你在下一次渲染時傳遞不同的值,React 將會更新所有讀取它的下層組件!這就是 context 經常和 state 結合使用的原因。 通常來說,如果樹中不同部分的遠距離組件需要傳遞某些信息,使用 context 是一個很好的選擇。 * Context 讓一個組件向其下整個樹的組件提供信息。 * 傳遞 context 的步驟: 1. 通過 `export const MyContext = createContext(defaultValue)` 創建並導出 context。 2. 在無論層級多深的任何子組件中,把 context 傳遞給 `useContext(MyContext)` Hook 來讀取它。 3. 在父組件中把 children 包裹在 `` 中來提供 context。 * Context 會穿過在中間的所有組件。 * Context 讓你寫出「適應周圍環境的」組件。 * 在你使用 context 之前,先嘗試下傳遞 props 或者將 JSX 作為 `children` 傳遞。 #### 用 context 代替 prop 逐級傳遞 {/*replace-prop-drilling-with-context*/} 該示例中,切換復選框狀態會修改傳入的每個 `` 的 `imageSize` 參數。復選框的 state 保存在頂層的 `App` 組件中,但每個 `` 都需要注意它。 现在, `App` 将 `imageSize` 傳遞給 `List`,再將其傳遞給每個 `Place`,`Place` 又將其傳遞給 `PlaceImage`。刪除 `imageSize` 參數,然後在 `App` 組件中直接將其傳遞給 `PlaceImage`。 你可以在 `Context.js` 中聲明 context。 ```js src/App.js import { useState } from 'react'; import { places } from './data.js'; import { getImageUrl } from './utils.js'; export default function App() { const [isLarge, setIsLarge] = useState(false); const imageSize = isLarge ? 150 : 100; return ( <>
    ) } function List({ imageSize }) { const listItems = places.map(place =>
  • ); return
      {listItems}
    ; } function Place({ place, imageSize }) { return ( <>

    {place.name} {': ' + place.description}

    ); } function PlaceImage({ place, imageSize }) { return ( {place.name} ); } ``` ```js src/Context.js ``` ```js src/data.js export const places = [{ id: 0, name: 'Bo-Kaap in Cape Town, South Africa', description: 'The tradition of choosing bright colors for houses began in the late 20th century.', imageId: 'K9HVAGH' }, { id: 1, name: 'Rainbow Village in Taichung, Taiwan', description: 'To save the houses from demolition, Huang Yung-Fu, a local resident, painted all 1,200 of them in 1924.', imageId: '9EAYZrt' }, { id: 2, name: 'Macromural de Pachuca, Mexico', description: 'One of the largest murals in the world covering homes in a hillside neighborhood.', imageId: 'DgXHVwu' }, { id: 3, name: 'Selarón Staircase in Rio de Janeiro, Brazil', description: 'This landmark was created by Jorge Selarón, a Chilean-born artist, as a "tribute to the Brazilian people."', imageId: 'aeO3rpI' }, { id: 4, name: 'Burano, Italy', description: 'The houses are painted following a specific color system dating back to 16th century.', imageId: 'kxsph5C' }, { id: 5, name: 'Chefchaouen, Marocco', description: 'There are a few theories on why the houses are painted blue, including that the color repels mosquitos or that it symbolizes sky and heaven.', imageId: 'rTqKo46' }, { id: 6, name: 'Gamcheon Culture Village in Busan, South Korea', description: 'In 2009, the village was converted into a cultural hub by painting the houses and featuring exhibitions and art installations.', imageId: 'ZfQOOzf' }]; ``` ```js src/utils.js export function getImageUrl(place) { return ( 'https://i.imgur.com/' + place.imageId + 'l.jpg' ); } ``` ```css ul { list-style-type: none; padding: 0px 10px; } li { margin-bottom: 10px; display: grid; grid-template-columns: auto 1fr; gap: 20px; align-items: center; } ```
    Remove `imageSize` prop from all the components. Create and export `ImageSizeContext` from `Context.js`. Then wrap the List into `` to pass the value down, and `useContext(ImageSizeContext)` to read it in the `PlaceImage`: ```js src/App.js import { useState, useContext } from 'react'; import { places } from './data.js'; import { getImageUrl } from './utils.js'; import { ImageSizeContext } from './Context.js'; export default function App() { const [isLarge, setIsLarge] = useState(false); const imageSize = isLarge ? 150 : 100; return (
    ) } function List() { const listItems = places.map(place =>
  • ); return
      {listItems}
    ; } function Place({ place }) { return ( <>

    {place.name} {': ' + place.description}

    ); } function PlaceImage({ place }) { const imageSize = useContext(ImageSizeContext); return ( {place.name} ); } ``` ```js src/Context.js import { createContext } from 'react'; export const ImageSizeContext = createContext(500); ``` ```js src/data.js export const places = [{ id: 0, name: 'Bo-Kaap in Cape Town, South Africa', description: 'The tradition of choosing bright colors for houses began in the late 20th century.', imageId: 'K9HVAGH' }, { id: 1, name: 'Rainbow Village in Taichung, Taiwan', description: 'To save the houses from demolition, Huang Yung-Fu, a local resident, painted all 1,200 of them in 1924.', imageId: '9EAYZrt' }, { id: 2, name: 'Macromural de Pachuca, Mexico', description: 'One of the largest murals in the world covering homes in a hillside neighborhood.', imageId: 'DgXHVwu' }, { id: 3, name: 'Selarón Staircase in Rio de Janeiro, Brazil', description: 'This landmark was created by Jorge Selarón, a Chilean-born artist, as a "tribute to the Brazilian people".', imageId: 'aeO3rpI' }, { id: 4, name: 'Burano, Italy', description: 'The houses are painted following a specific color system dating back to 16th century.', imageId: 'kxsph5C' }, { id: 5, name: 'Chefchaouen, Marocco', description: 'There are a few theories on why the houses are painted blue, including that the color repels mosquitos or that it symbolizes sky and heaven.', imageId: 'rTqKo46' }, { id: 6, name: 'Gamcheon Culture Village in Busan, South Korea', description: 'In 2009, the village was converted into a cultural hub by painting the houses and featuring exhibitions and art installations.', imageId: 'ZfQOOzf' }]; ``` ```js src/utils.js export function getImageUrl(place) { return ( 'https://i.imgur.com/' + place.imageId + 'l.jpg' ); } ``` ```css ul { list-style-type: none; padding: 0px 10px; } li { margin-bottom: 10px; display: grid; grid-template-columns: auto 1fr; gap: 20px; align-items: center; } ```
    注意,中間的組件不再需要傳遞 `imageSize`。
    \ No newline at end of file +--- +title: 使用Context深層傳遞參數 +--- + + + +大多數情況下,你會通過 props 將信息從父組件傳遞給子組件。但是,如果你必須通過許多中間組件向下傳遞 props,或者是在你應用的許多組件中需要傳遞相同的信息,傳遞props會變得冗長和不便。 *Context* 不需要通過 props 顯示傳遞,它允許父組件向其下層的無論多深的任意組件傳遞信息, + + + + + +- 什麼是「prop 逐級傳遞」 +- 如何使用 context 替代重複的參數傳遞 +- Context 的常見用法 +- Context 的常用代替方案 + + + +## 傳遞 props 帶來的問題 {/*the-problem-with-passing-props*/} + +[傳遞 props](/learn/passing-props-to-a-component) 是一種將數據通過 UI 樹顯式傳遞到使用它的組件的好方法。 + +但是當你需要在組件樹中深層傳遞參數以及需要在組件間複用相同的參數時,傳遞 props 就會變得冗長且不便。最近的根節點父組件可能離需要數據的組件很遠,[狀態提升](/learn/sharing-state-between-components)至過高的層級會導致「prop 逐級傳遞」的情況。 + + + + + +狀態提升 + + + + +Prop 逐級傳遞 + + + + + +要是有一種方法能夠在組件樹中「直傳」數據到所需的組件還不用通過 props,可就太好了。React 的 context 功能可以做到。 + +## Context: 傳遞 props 的另一種方法 {/*context-an-alternative-to-passing-props*/} + +Context 讓父組件可以為它下面的整個組件樹提供數據。Context 有很多種用途。這有一個示例。思考一下這個 `Heading` 組件接收一個 `level` 參數決定它標題尺寸的場景: + + + +```js +import Heading from './Heading.js'; +import Section from './Section.js'; + +export default function Page() { + return ( +
    + Title + Heading + Sub-heading + Sub-sub-heading + Sub-sub-sub-heading + Sub-sub-sub-sub-heading +
    + ); +} +``` + +```js src/Section.js +export default function Section({ children }) { + return ( +
    + {children} +
    + ); +} +``` + +```js src/Heading.js +export default function Heading({ level, children }) { + switch (level) { + case 1: + return

    {children}

    ; + case 2: + return

    {children}

    ; + case 3: + return

    {children}

    ; + case 4: + return

    {children}

    ; + case 5: + return
    {children}
    ; + case 6: + return
    {children}
    ; + default: + throw Error('Unknown level: ' + level); + } +} +``` + +```css +.section { + padding: 10px; + margin: 5px; + border-radius: 5px; + border: 1px solid #aaa; +} +``` + +
    + +假如你想讓相同 `Section` 中的多個 Heading 總是有相同的尺寸: + + + +```js +import Heading from './Heading.js'; +import Section from './Section.js'; + +export default function Page() { + return ( +
    + Title +
    + Heading + Heading + Heading +
    + Sub-heading + Sub-heading + Sub-heading +
    + Sub-sub-heading + Sub-sub-heading + Sub-sub-heading +
    +
    +
    +
    + ); +} +``` + +```js src/Section.js +export default function Section({ children }) { + return ( +
    + {children} +
    + ); +} +``` + +```js src/Heading.js +export default function Heading({ level, children }) { + switch (level) { + case 1: + return

    {children}

    ; + case 2: + return

    {children}

    ; + case 3: + return

    {children}

    ; + case 4: + return

    {children}

    ; + case 5: + return
    {children}
    ; + case 6: + return
    {children}
    ; + default: + throw Error('Unknown level: ' + level); + } +} +``` + +```css +.section { + padding: 10px; + margin: 5px; + border-radius: 5px; + border: 1px solid #aaa; +} +``` + +
    + +現在,你將 `level` 參數分別傳遞給每個 ``: + +```js +
    + About + Photos + Videos +
    +``` + +將 `level` 參數傳遞給 `
    ` 組件而不是傳給 `` 組件,會看起來更好一些。這樣的話你可以強制使同一個 section 中的所有標題都有相同的尺寸: + +```js +
    + About + Photos + Videos +
    +``` + +但是 `` 組件是如何知道離它最近的 `
    ` 的 level 的呢?**這需要子組件可以通過某種方式「訪問」到組件樹中某處在其上層的數據。** + +你不能只通過 props 來實現它。這就是 context 大展身手的地方。你可以通過以下三個步驟實現它: + +1. **創建** 一個 context。 (你可以呼叫它為 `LevelContext`, 因為它表示的是標題級別。) +2. 在需要數據的組件內 **使用** 剛剛創建的 context。(`Heading` 將會使用 `LevelContext`。) +3. 在指定數據的組件中 **提供** 這個 context(`Section` 將會提供 `LevelContext`。) + +Context 可以讓父節點,甚至是很遠的父節點都可以為其內部的整個組件樹提供數據。 + + + + + +同級子組件使用 context + + + + + +遠親組件使用 context + + + + + +### Step 1: 創建 context {/*step-1-create-the-context*/} + +首先, 你需要創建這個 context. 你需要將其 **從一個文件中導出** 來讓你的組件可以使用它: + + + +```js +import Heading from './Heading.js'; +import Section from './Section.js'; + +export default function Page() { + return ( +
    + Title +
    + Heading + Heading + Heading +
    + Sub-heading + Sub-heading + Sub-heading +
    + Sub-sub-heading + Sub-sub-heading + Sub-sub-heading +
    +
    +
    +
    + ); +} +``` + +```js src/Section.js +export default function Section({ children }) { + return ( +
    + {children} +
    + ); +} +``` + +```js src/Heading.js +export default function Heading({ level, children }) { + switch (level) { + case 1: + return

    {children}

    ; + case 2: + return

    {children}

    ; + case 3: + return

    {children}

    ; + case 4: + return

    {children}

    ; + case 5: + return
    {children}
    ; + case 6: + return
    {children}
    ; + default: + throw Error('Unknown level: ' + level); + } +} +``` + +```js src/LevelContext.js active +import { createContext } from 'react'; + +export const LevelContext = createContext(1); +``` + +```css +.section { + padding: 10px; + margin: 5px; + border-radius: 5px; + border: 1px solid #aaa; +} +``` + +
    + +`createContext` 只需要 _default_ value。 這裡,`1` 代表最大的標題級別,但是你可以傳遞任何類型的 value(甚至是一個對象)。你將在下一個步驟中見識到 default value 的意義。 + +### Step 2: Use the context {/*step-2-use-the-context*/} + +從 React 中引入 `useContext` Hook 以及你剛剛創建的 context: + +```js +import { useContext } from 'react'; +import { LevelContext } from './LevelContext.js'; +``` + +現在,`Heading` 組件從 props 中讀取 `level`: + +```js +export default function Heading({ level, children }) { + // ... +} +``` + +然後,刪除 `level` 參數並從你剛剛引入的 `LevelContext` 中讀取值: + +```js {2} +export default function Heading({ children }) { + const level = useContext(LevelContext); + // ... +} +``` + +`useContext` 是一個 Hook。 就像 `useState` 和 `useReducer` 一樣, 你只能在 React 組件中(不是循環或者條件裡)立即調用 Hook。 **`useContext` 告訴 React `Heading` 組件想要讀取 `LevelContext`。** + +現在 `Heading` 組件中沒有 `level` 參數, 你不再需要像這樣在你的 JSX 中將 level 參數傳遞給 `Heading`: + +```js +
    + Sub-sub-heading + Sub-sub-heading + Sub-sub-heading +
    +``` + +更新一下 JSX 來讓 `Section` 組件可以接收 level 參數: + +```jsx +
    + Sub-sub-heading + Sub-sub-heading + Sub-sub-heading +
    +``` + +提醒一下,這是你使得代碼能正常運行的必備步驟: + + + +```js +import Heading from './Heading.js'; +import Section from './Section.js'; + +export default function Page() { + return ( +
    + Title +
    + Heading + Heading + Heading +
    + Sub-heading + Sub-heading + Sub-heading +
    + Sub-sub-heading + Sub-sub-heading + Sub-sub-heading +
    +
    +
    +
    + ); +} +``` + +```js src/Section.js +export default function Section({ children }) { + return ( +
    + {children} +
    + ); +} +``` + +```js src/Heading.js +import { useContext } from 'react'; +import { LevelContext } from './LevelContext.js'; + +export default function Heading({ children }) { + const level = useContext(LevelContext); + switch (level) { + case 1: + return

    {children}

    ; + case 2: + return

    {children}

    ; + case 3: + return

    {children}

    ; + case 4: + return

    {children}

    ; + case 5: + return
    {children}
    ; + case 6: + return
    {children}
    ; + default: + throw Error('Unknown level: ' + level); + } +} +``` + +```js src/LevelContext.js +import { createContext } from 'react'; + +export const LevelContext = createContext(1); +``` + +```css +.section { + padding: 10px; + margin: 5px; + border-radius: 5px; + border: 1px solid #aaa; +} +``` + +
    + +注意,這個示例還不能運行!所有的 headings 的尺寸都一樣,因為 **哪怕你正在 *使用* context, 你也還沒 *提供* 它。** React 不知道從哪獲取這個 context! + +如果你不提供 context, React 將會使用你在上一步指定的默認值。在這個例子中,你將參數 `1` 傳遞給了 `createContext`,因此 `useContext(LevelContext)` 會返回 `1`,同時把所有的標題都設置為 `

    `。我们可以通过让每个 `Section` 提供它自己的 context 来修复这个问题。 + +### Step 3: 提供 context {/*step-3-provide-the-context*/} + +`Section` 組件目前渲染傳入它的子組件: + +```js +export default function Section({ children }) { + return ( +
    + {children} +
    + ); +} +``` + +**用一個 context provider 把它们包裹起来** 便可以提供 `LevelContext` 給它們: + + +```js {1,6,8} +import { LevelContext } from './LevelContext.js'; + +export default function Section({ level, children }) { + return ( +
    + + {children} + +
    + ); +} +``` + +這會告訴 React:「如果在 `
    ` 組件中的任何子組件請求 `LevelContext`,給它們這個 `level`。」組件會使用 UI 樹中在它上層最近的那個 `` 傳遞過來的值。 + + + +```js +import Heading from './Heading.js'; +import Section from './Section.js'; + +export default function Page() { + return ( +
    + Title +
    + Heading + Heading + Heading +
    + Sub-heading + Sub-heading + Sub-heading +
    + Sub-sub-heading + Sub-sub-heading + Sub-sub-heading +
    +
    +
    +
    + ); +} +``` + +```js src/Section.js +import { LevelContext } from './LevelContext.js'; + +export default function Section({ level, children }) { + return ( +
    + + {children} + +
    + ); +} +``` + +```js src/Heading.js +import { useContext } from 'react'; +import { LevelContext } from './LevelContext.js'; + +export default function Heading({ children }) { + const level = useContext(LevelContext); + switch (level) { + case 1: + return

    {children}

    ; + case 2: + return

    {children}

    ; + case 3: + return

    {children}

    ; + case 4: + return

    {children}

    ; + case 5: + return
    {children}
    ; + case 6: + return
    {children}
    ; + default: + throw Error('Unknown level: ' + level); + } +} +``` + +```js src/LevelContext.js +import { createContext } from 'react'; + +export const LevelContext = createContext(1); +``` + +```css +.section { + padding: 10px; + margin: 5px; + border-radius: 5px; + border: 1px solid #aaa; +} +``` + +
    + +這與原始代碼的運行結果是相同的,但是你不需要傳遞 `level` 參數給每一個 `Heading` 組件了!取而代之, 它通過訪問上層最近的 `Section` 來「斷定」它的標題級別: + +1. 你將一個 `level` 參數傳給 `
    `。 +2. `Section` 把它的子元素包裹在 `` 中。 +3. `Heading` 使用 `useContext(LevelContext)` 訪問上層最近的 `LevelContext` 提供的值。 + +## 在相同的组件中使用并提供 context {/*using-and-providing-context-from-the-same-component*/} + +現在,你仍需要手動指定每個 section 的 `level`: + +```js +export default function Page() { + return ( +
    + ... +
    + ... +
    + ... +``` + +由於 context 讓你可以從上層的組件中讀取信息,每個 `Section` 都會從上層的 `Section` 讀取 `level`,並自動向下層傳遞 `level + 1`。你可以像下面這樣做。 + +```js src/Section.js {5,8} +import { useContext } from 'react'; +import { LevelContext } from './LevelContext.js'; + +export default function Section({ children }) { + const level = useContext(LevelContext); + return ( +
    + + {children} + +
    + ); +} +``` + +這樣修改後,你不需要將 `level` 參數傳給 `
    ` *或者是* `` 了: + + + +```js +import Heading from './Heading.js'; +import Section from './Section.js'; + +export default function Page() { + return ( +
    + Title +
    + Heading + Heading + Heading +
    + Sub-heading + Sub-heading + Sub-heading +
    + Sub-sub-heading + Sub-sub-heading + Sub-sub-heading +
    +
    +
    +
    + ); +} +``` + +```js src/Section.js +import { useContext } from 'react'; +import { LevelContext } from './LevelContext.js'; + +export default function Section({ children }) { + const level = useContext(LevelContext); + return ( +
    + + {children} + +
    + ); +} +``` + +```js src/Heading.js +import { useContext } from 'react'; +import { LevelContext } from './LevelContext.js'; + +export default function Heading({ children }) { + const level = useContext(LevelContext); + switch (level) { + case 0: + throw Error('Heading must be inside a Section!'); + case 1: + return

    {children}

    ; + case 2: + return

    {children}

    ; + case 3: + return

    {children}

    ; + case 4: + return

    {children}

    ; + case 5: + return
    {children}
    ; + case 6: + return
    {children}
    ; + default: + throw Error('Unknown level: ' + level); + } +} +``` + +```js src/LevelContext.js +import { createContext } from 'react'; + +export const LevelContext = createContext(0); +``` + +```css +.section { + padding: 10px; + margin: 5px; + border-radius: 5px; + border: 1px solid #aaa; +} +``` + +
    + +現在,`Heading` 和 `Section` 都通過讀取 `LevelContext` 來判斷它們「深度」。並且 `Section` 把它的子組件都包裹在 `LevelContext` 中來指定其中的任何內容都處於一個「更深」的級別。 + + + +該示例使用標題級別來說明,是因為它們直觀地顯示了嵌套組件是如何覆蓋 context。但是 context 對於許多其他場景也很有用。你可以用它來傳遞整個字數需要的任何信息: 當前的顏色主題、當前的登錄用戶等。 + + + +## Context 能穿過中間層級的組件 {/*context-passes-through-intermediate-components*/} + +你可以在提供 context 的組件和使用它的組件之間的層級插入任意數量的組件。這包括像 `
    ` 這樣的內置組件和你自己創建的組件。 + +該實例中,相同的 `Post` 組件(帶有虛線邊框)在兩個不同的嵌套層級上渲染。注意,它內部的 `` 會自動從最近的 `
    ` 獲取它的級別: + + + +```js +import Heading from './Heading.js'; +import Section from './Section.js'; + +export default function ProfilePage() { + return ( +
    + My Profile + + +
    + ); +} + +function AllPosts() { + return ( +
    + Posts + +
    + ); +} + +function RecentPosts() { + return ( +
    + Recent Posts + + +
    + ); +} + +function Post({ title, body }) { + return ( +
    + + {title} + +

    {body}

    +
    + ); +} +``` + +```js src/Section.js +import { useContext } from 'react'; +import { LevelContext } from './LevelContext.js'; + +export default function Section({ children, isFancy }) { + const level = useContext(LevelContext); + return ( +
    + + {children} + +
    + ); +} +``` + +```js src/Heading.js +import { useContext } from 'react'; +import { LevelContext } from './LevelContext.js'; + +export default function Heading({ children }) { + const level = useContext(LevelContext); + switch (level) { + case 0: + throw Error('Heading must be inside a Section!'); + case 1: + return

    {children}

    ; + case 2: + return

    {children}

    ; + case 3: + return

    {children}

    ; + case 4: + return

    {children}

    ; + case 5: + return
    {children}
    ; + case 6: + return
    {children}
    ; + default: + throw Error('Unknown level: ' + level); + } +} +``` + +```js src/LevelContext.js +import { createContext } from 'react'; + +export const LevelContext = createContext(0); +``` + +```css +.section { + padding: 10px; + margin: 5px; + border-radius: 5px; + border: 1px solid #aaa; +} + +.fancy { + border: 4px dashed pink; +} +``` + +
    + +你不需要做任何特殊的操作。`Section` 為它內部的樹指定了一個 context,所以你可以在任何地方插入一個 ``,並且它會有正確的尺寸。在上邊的 sandbox 嘗試下! + +**Context 讓你可以編寫「適應周圍的環境」的組件,並且根據 _在哪_ (或者說,_在哪個 context 中_)來渲染它們不同的樣子。** + +Context 的工作方式可能會讓你想起[CSS 屬性繼承](https://developer.mozilla.org/en-US/docs/Web/CSS/inheritance) 在 CSS, 你可以指定 `color: blue` 給一個 `
    `, 並且在其中的任意 DOM 節點,無論多深,都會繼承那個顏色,除非中間的其他 DOM 節點用 `color: green` 覆蓋它。類似地,在 React 中,覆蓋來自上層的某些 Context 的唯一方法時間子組件包裹到一個提供不同值的 context provider 中。 + +在 CSS 中,不同屬性的 `color` 和 `background-color` 不會彼此覆蓋。你可以設置所有的 `
    ` 都為 `color` 紅色,還不會影響 `background-color`。類似地, **不同的 React context 不會彼此覆蓋。** 你用 `createContext()` 創建的每個 context 都和其他 context 完全分離,只有使用和提供 *那個特定的* context 才會聯繫到一起。一個組件可以毫無問題地使用或者是提供不同的 context。 + +## 在你使用 context 之前 {/*before-you-use-context*/} + +使用 Context 看起來非常誘人!然而,這也意味著它很容易被過度使用。 **若你只是想將一些 props 逐層傳遞多個層級,這並不意味著你需要把這些信息放到 context 中。** + +在你使用 context 之前,這裡有一些可供你選擇的代替方案: + +1. **從 [傳遞 props 開始。](/learn/passing-props-to-a-component)** 如果你的組件看起來不起眼,那麼通過十幾個組件向下傳遞一堆 props 並不少見。這有點像是在埋頭苦幹,但是這樣做可以讓哪些組件用了哪些數據變得十分清晰!維護你代碼的人會很高興你用 props 傳遞數據,這會讓數據流變得更加清晰。 +2. **抽象組件並 [把 JSX 作為 `children`](/learn/passing-props-to-a-component#passing-jsx-as-children) 傳給它們。** 如果你通過很多層不使用該數據的中間組件(並且只會向下傳遞)來傳遞數據。舉個例子,你可能想把一些像 `posts` 的 props 數據傳遞到不會直接使用這個參數的組件,比如說 ``。更好的方式是,讓 `Layout` 把 `children` 當做一個參數,然後渲染 ``。這樣就減少了定義數據的組件和使用數據的組件之間的層級。 + +如果這兩種替代方案都不適合你,再考慮使用 context。 + +## Context 的使用場景 {/*use-cases-for-context*/} + +* **主題:** 如果你的應用允許用戶更改其外觀(例如 dark mode),你可以在應用頂層定義一個 context provider,並在需要調整其外觀的組件中使用該 context。 +* **當前賬戶:** 許多組件可能需要知道當前登錄的用戶信息。把它放到 context 中可以方便在樹中任何為止讀取它。某些應用還允許你同時操作多個賬戶(例如,以不同用戶的身份發表評論)。在這些情況下,將 UI 的一部分包裹到具有不同賬戶數據的 provider 中會很方便。 +* **路由:** 大多數路由解決方案在其內部使用 context 來保存當前路由。這就是每個鏈接「知道」它是否處於活動狀態的方式。如果你創建自己的路由,你可能也會這麼做。 +* **狀態管理:** 隨著你的應用的開發,最終在靠近應用頂部的為止可能會有很多 state。許多遙遠的下層組件可能想要修改它們。通常 [將 reducer 與 context 搭配使用](/learn/scaling-up-with-reducer-and-context) 來管理複雜的狀態並將其傳遞給深層的組件來避免過多的麻煩。 + +Context 不局限於靜態值。如果你在下一次渲染時傳遞不同的值,React 將會更新所有讀取它的下層組件!這就是 context 經常和 state 結合使用的原因。 + +通常來說,如果樹中不同部分的遠距離組件需要傳遞某些信息,使用 context 是一個很好的選擇。 + + + +* Context 讓一個組件向其下整個樹的組件提供信息。 +* 傳遞 context 的步驟: + 1. 通過 `export const MyContext = createContext(defaultValue)` 創建並導出 context。 + 2. 在無論層級多深的任何子組件中,把 context 傳遞給 `useContext(MyContext)` Hook 來讀取它。 + 3. 在父組件中把 children 包裹在 `` 中來提供 context。 +* Context 會穿過在中間的所有組件。 +* Context 讓你寫出「適應周圍環境的」組件。 +* 在你使用 context 之前,先嘗試下傳遞 props 或者將 JSX 作為 `children` 傳遞。 + + + + + +#### 用 context 代替 prop 逐級傳遞 {/*replace-prop-drilling-with-context*/} + +該示例中,切換復選框狀態會修改傳入的每個 `` 的 `imageSize` 參數。復選框的 state 保存在頂層的 `App` 組件中,但每個 `` 都需要注意它。 + +现在, `App` 将 `imageSize` 傳遞給 `List`,再將其傳遞給每個 `Place`,`Place` 又將其傳遞給 `PlaceImage`。刪除 `imageSize` 參數,然後在 `App` 組件中直接將其傳遞給 `PlaceImage`。 + +你可以在 `Context.js` 中聲明 context。 + + + +```js src/App.js +import { useState } from 'react'; +import { places } from './data.js'; +import { getImageUrl } from './utils.js'; + +export default function App() { + const [isLarge, setIsLarge] = useState(false); + const imageSize = isLarge ? 150 : 100; + return ( + <> + +
    + + + ) +} + +function List({ imageSize }) { + const listItems = places.map(place => +
  • + +
  • + ); + return
      {listItems}
    ; +} + +function Place({ place, imageSize }) { + return ( + <> + +

    + {place.name} + {': ' + place.description} +

    + + ); +} + +function PlaceImage({ place, imageSize }) { + return ( + {place.name} + ); +} +``` + +```js src/Context.js + +``` + +```js src/data.js +export const places = [{ + id: 0, + name: 'Bo-Kaap in Cape Town, South Africa', + description: 'The tradition of choosing bright colors for houses began in the late 20th century.', + imageId: 'K9HVAGH' +}, { + id: 1, + name: 'Rainbow Village in Taichung, Taiwan', + description: 'To save the houses from demolition, Huang Yung-Fu, a local resident, painted all 1,200 of them in 1924.', + imageId: '9EAYZrt' +}, { + id: 2, + name: 'Macromural de Pachuca, Mexico', + description: 'One of the largest murals in the world covering homes in a hillside neighborhood.', + imageId: 'DgXHVwu' +}, { + id: 3, + name: 'Selarón Staircase in Rio de Janeiro, Brazil', + description: 'This landmark was created by Jorge Selarón, a Chilean-born artist, as a "tribute to the Brazilian people."', + imageId: 'aeO3rpI' +}, { + id: 4, + name: 'Burano, Italy', + description: 'The houses are painted following a specific color system dating back to 16th century.', + imageId: 'kxsph5C' +}, { + id: 5, + name: 'Chefchaouen, Marocco', + description: 'There are a few theories on why the houses are painted blue, including that the color repels mosquitos or that it symbolizes sky and heaven.', + imageId: 'rTqKo46' +}, { + id: 6, + name: 'Gamcheon Culture Village in Busan, South Korea', + description: 'In 2009, the village was converted into a cultural hub by painting the houses and featuring exhibitions and art installations.', + imageId: 'ZfQOOzf' +}]; +``` + +```js src/utils.js +export function getImageUrl(place) { + return ( + 'https://i.imgur.com/' + + place.imageId + + 'l.jpg' + ); +} +``` + +```css +ul { list-style-type: none; padding: 0px 10px; } +li { + margin-bottom: 10px; + display: grid; + grid-template-columns: auto 1fr; + gap: 20px; + align-items: center; +} +``` + +
    + + + +Remove `imageSize` prop from all the components. + +Create and export `ImageSizeContext` from `Context.js`. Then wrap the List into `` to pass the value down, and `useContext(ImageSizeContext)` to read it in the `PlaceImage`: + + + +```js src/App.js +import { useState, useContext } from 'react'; +import { places } from './data.js'; +import { getImageUrl } from './utils.js'; +import { ImageSizeContext } from './Context.js'; + +export default function App() { + const [isLarge, setIsLarge] = useState(false); + const imageSize = isLarge ? 150 : 100; + return ( + + +
    + +
    + ) +} + +function List() { + const listItems = places.map(place => +
  • + +
  • + ); + return
      {listItems}
    ; +} + +function Place({ place }) { + return ( + <> + +

    + {place.name} + {': ' + place.description} +

    + + ); +} + +function PlaceImage({ place }) { + const imageSize = useContext(ImageSizeContext); + return ( + {place.name} + ); +} +``` + +```js src/Context.js +import { createContext } from 'react'; + +export const ImageSizeContext = createContext(500); +``` + +```js src/data.js +export const places = [{ + id: 0, + name: 'Bo-Kaap in Cape Town, South Africa', + description: 'The tradition of choosing bright colors for houses began in the late 20th century.', + imageId: 'K9HVAGH' +}, { + id: 1, + name: 'Rainbow Village in Taichung, Taiwan', + description: 'To save the houses from demolition, Huang Yung-Fu, a local resident, painted all 1,200 of them in 1924.', + imageId: '9EAYZrt' +}, { + id: 2, + name: 'Macromural de Pachuca, Mexico', + description: 'One of the largest murals in the world covering homes in a hillside neighborhood.', + imageId: 'DgXHVwu' +}, { + id: 3, + name: 'Selarón Staircase in Rio de Janeiro, Brazil', + description: 'This landmark was created by Jorge Selarón, a Chilean-born artist, as a "tribute to the Brazilian people".', + imageId: 'aeO3rpI' +}, { + id: 4, + name: 'Burano, Italy', + description: 'The houses are painted following a specific color system dating back to 16th century.', + imageId: 'kxsph5C' +}, { + id: 5, + name: 'Chefchaouen, Marocco', + description: 'There are a few theories on why the houses are painted blue, including that the color repels mosquitos or that it symbolizes sky and heaven.', + imageId: 'rTqKo46' +}, { + id: 6, + name: 'Gamcheon Culture Village in Busan, South Korea', + description: 'In 2009, the village was converted into a cultural hub by painting the houses and featuring exhibitions and art installations.', + imageId: 'ZfQOOzf' +}]; +``` + +```js src/utils.js +export function getImageUrl(place) { + return ( + 'https://i.imgur.com/' + + place.imageId + + 'l.jpg' + ); +} +``` + +```css +ul { list-style-type: none; padding: 0px 10px; } +li { + margin-bottom: 10px; + display: grid; + grid-template-columns: auto 1fr; + gap: 20px; + align-items: center; +} +``` + +
    + +注意,中間的組件不再需要傳遞 `imageSize`。 + +
    + +
    From 52312057a626331f2ab88533ec95a3b03b46bc59 Mon Sep 17 00:00:00 2001 From: ShenHdou <15574380273@163.com> Date: Tue, 23 Apr 2024 00:21:25 +0800 Subject: [PATCH 4/5] docs: translate src/content/learn/passing-data-deeply-with-context.md --- src/content/learn/passing-data-deeply-with-context.md | 1 - 1 file changed, 1 deletion(-) diff --git a/src/content/learn/passing-data-deeply-with-context.md b/src/content/learn/passing-data-deeply-with-context.md index 43291528c..d1ba1de06 100644 --- a/src/content/learn/passing-data-deeply-with-context.md +++ b/src/content/learn/passing-data-deeply-with-context.md @@ -462,7 +462,6 @@ export default function Section({ children }) { **用一個 context provider 把它们包裹起来** 便可以提供 `LevelContext` 給它們: - ```js {1,6,8} import { LevelContext } from './LevelContext.js'; From c8b65415430c969fe2a844d3cba4f47b36a681b2 Mon Sep 17 00:00:00 2001 From: ShenHdou <15574380273@163.com> Date: Tue, 23 Apr 2024 02:03:41 +0800 Subject: [PATCH 5/5] docs: translate src/content/learn/passing-data-deeply-with-context.md --- .../learn/passing-data-deeply-with-context.md | 98 +++++++++---------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/src/content/learn/passing-data-deeply-with-context.md b/src/content/learn/passing-data-deeply-with-context.md index d1ba1de06..38faf520e 100644 --- a/src/content/learn/passing-data-deeply-with-context.md +++ b/src/content/learn/passing-data-deeply-with-context.md @@ -1,10 +1,10 @@ --- -title: 使用Context深層傳遞參數 +title: 使用 Context 深層傳遞參數 --- -大多數情況下,你會通過 props 將信息從父組件傳遞給子組件。但是,如果你必須通過許多中間組件向下傳遞 props,或者是在你應用的許多組件中需要傳遞相同的信息,傳遞props會變得冗長和不便。 *Context* 不需要通過 props 顯示傳遞,它允許父組件向其下層的無論多深的任意組件傳遞信息, +大多數情況下,你會通過 props 將信息從 parent component 傳遞給 child component。但是,如果你必須通過許多中間 component 向下傳遞 props,或者是在你應用的許多 component 中需要傳遞相同的信息,傳遞props會變得冗長和不便。 *Context* 不需要通過 props 顯示傳遞,它允許 parent component 向其下層的無論多深的任意 component 傳遞信息, @@ -19,15 +19,15 @@ title: 使用Context深層傳遞參數 ## 傳遞 props 帶來的問題 {/*the-problem-with-passing-props*/} -[傳遞 props](/learn/passing-props-to-a-component) 是一種將數據通過 UI 樹顯式傳遞到使用它的組件的好方法。 +[傳遞 props](/learn/passing-props-to-a-component) 是一種將數據通過 UI 樹顯式傳遞到使用它的 component 的好方法。 -但是當你需要在組件樹中深層傳遞參數以及需要在組件間複用相同的參數時,傳遞 props 就會變得冗長且不便。最近的根節點父組件可能離需要數據的組件很遠,[狀態提升](/learn/sharing-state-between-components)至過高的層級會導致「prop 逐級傳遞」的情況。 +但是當你需要在 tree 中深層傳遞參數以及需要在 component 間複用相同的參數時,傳遞 props 就會變得冗長且不便。最近的共同祖先可能離需要數據的 component 很遠,[state 提升](/learn/sharing-state-between-components)至過高的層級會導致「prop 逐級傳遞」的情況。 -狀態提升 +state 提升 @@ -38,11 +38,11 @@ Prop 逐級傳遞 -要是有一種方法能夠在組件樹中「直傳」數據到所需的組件還不用通過 props,可就太好了。React 的 context 功能可以做到。 +要是有一種方法能夠在 tree 中「直傳」數據到所需的 component 還不用通過 props,可就太好了。React 的 context 功能可以做到。 ## Context: 傳遞 props 的另一種方法 {/*context-an-alternative-to-passing-props*/} -Context 讓父組件可以為它下面的整個組件樹提供數據。Context 有很多種用途。這有一個示例。思考一下這個 `Heading` 組件接收一個 `level` 參數決定它標題尺寸的場景: +Context 讓 parent component 可以為它下面的整個 tree 提供數據。Context 有很多種用途。這有一個示例。思考一下這個 `Heading` component 接收一個 `level` 參數決定它標題尺寸的場景: @@ -190,7 +190,7 @@ export default function Heading({ level, children }) {
    ``` -將 `level` 參數傳遞給 `
    ` 組件而不是傳給 `` 組件,會看起來更好一些。這樣的話你可以強制使同一個 section 中的所有標題都有相同的尺寸: +將 `level` 參數傳遞給 `
    ` component 而不是傳給 `` component ,會看起來更好一些。這樣的話你可以強制使同一個 section 中的所有標題都有相同的尺寸: ```js
    @@ -200,27 +200,27 @@ export default function Heading({ level, children }) {
    ``` -但是 `` 組件是如何知道離它最近的 `
    ` 的 level 的呢?**這需要子組件可以通過某種方式「訪問」到組件樹中某處在其上層的數據。** +但是 `` component 是如何知道離它最近的 `
    ` 的 level 的呢?**這需要 child 可以通過某種方式「訪問」到 tree 中某處在其上層的數據。** 你不能只通過 props 來實現它。這就是 context 大展身手的地方。你可以通過以下三個步驟實現它: 1. **創建** 一個 context。 (你可以呼叫它為 `LevelContext`, 因為它表示的是標題級別。) -2. 在需要數據的組件內 **使用** 剛剛創建的 context。(`Heading` 將會使用 `LevelContext`。) -3. 在指定數據的組件中 **提供** 這個 context(`Section` 將會提供 `LevelContext`。) +2. 在需要數據的 component 內 **使用** 剛剛創建的 context。(`Heading` 將會使用 `LevelContext`。) +3. 在指定數據的 component 中 **提供** 這個 context(`Section` 將會提供 `LevelContext`。) -Context 可以讓父節點,甚至是很遠的父節點都可以為其內部的整個組件樹提供數據。 +Context 可以讓 parent,甚至是很遠的一個都可以為其內部的整個 tree 提供數據。 -同級子組件使用 context +同級 child component 使用 context -遠親組件使用 context +遠親 component 使用 context @@ -228,7 +228,7 @@ Context 可以讓父節點,甚至是很遠的父節點都可以為其內部的 ### Step 1: 創建 context {/*step-1-create-the-context*/} -首先, 你需要創建這個 context. 你需要將其 **從一個文件中導出** 來讓你的組件可以使用它: +首先, 你需要創建這個 context. 你需要將其 **從一個文件中 export** 來讓你的 component 可以使用它: @@ -319,7 +319,7 @@ import { useContext } from 'react'; import { LevelContext } from './LevelContext.js'; ``` -現在,`Heading` 組件從 props 中讀取 `level`: +現在,`Heading` component 從 props 中讀取 `level`: ```js export default function Heading({ level, children }) { @@ -336,9 +336,9 @@ export default function Heading({ children }) { } ``` -`useContext` 是一個 Hook。 就像 `useState` 和 `useReducer` 一樣, 你只能在 React 組件中(不是循環或者條件裡)立即調用 Hook。 **`useContext` 告訴 React `Heading` 組件想要讀取 `LevelContext`。** +`useContext` 是一個 Hook。 就像 `useState` 和 `useReducer` 一樣, 你只能在 React component 中(不是循環或者條件裡)立即調用 Hook。 **`useContext` 告訴 React `Heading` component 想要讀取 `LevelContext`。** -現在 `Heading` 組件中沒有 `level` 參數, 你不再需要像這樣在你的 JSX 中將 level 參數傳遞給 `Heading`: +現在 `Heading` component 中沒有 `level` 參數, 你不再需要像這樣在你的 JSX 中將 level 參數傳遞給 `Heading`: ```js
    @@ -348,7 +348,7 @@ export default function Heading({ children }) {
    ``` -更新一下 JSX 來讓 `Section` 組件可以接收 level 參數: +更新一下 JSX 來讓 `Section` component 可以接收 level 參數: ```jsx
    @@ -444,11 +444,11 @@ export const LevelContext = createContext(1); 注意,這個示例還不能運行!所有的 headings 的尺寸都一樣,因為 **哪怕你正在 *使用* context, 你也還沒 *提供* 它。** React 不知道從哪獲取這個 context! -如果你不提供 context, React 將會使用你在上一步指定的默認值。在這個例子中,你將參數 `1` 傳遞給了 `createContext`,因此 `useContext(LevelContext)` 會返回 `1`,同時把所有的標題都設置為 `

    `。我们可以通过让每个 `Section` 提供它自己的 context 来修复这个问题。 +如果你不提供 context, React 將會使用你在上一步指定的默認值。在這個例子中,你將參數 `1` 傳遞給了 `createContext`,因此 `useContext(LevelContext)` 會 return `1`,同時把所有的標題都設置為 `

    `。我们可以通过让每个 `Section` 提供它自己的 context 来修复这个问题。 ### Step 3: 提供 context {/*step-3-provide-the-context*/} -`Section` 組件目前渲染傳入它的子組件: +`Section`component 目前渲染傳入它的 child component: ```js export default function Section({ children }) { @@ -476,7 +476,7 @@ export default function Section({ level, children }) { } ``` -這會告訴 React:「如果在 `
    ` 組件中的任何子組件請求 `LevelContext`,給它們這個 `level`。」組件會使用 UI 樹中在它上層最近的那個 `` 傳遞過來的值。 +這會告訴 React:「如果在 `
    ` component 中的任何 child component 請求 `LevelContext`,給它們這個 `level`。」 component 會使用 UI 樹中在它上層最近的那個 `` 傳遞過來的值。 @@ -564,10 +564,10 @@ export const LevelContext = createContext(1); -這與原始代碼的運行結果是相同的,但是你不需要傳遞 `level` 參數給每一個 `Heading` 組件了!取而代之, 它通過訪問上層最近的 `Section` 來「斷定」它的標題級別: +這與原始代碼的運行結果是相同的,但是你不需要傳遞 `level` 參數給每一個 `Heading` component 了!取而代之, 它通過訪問上層最近的 `Section` 來「斷定」它的標題級別: 1. 你將一個 `level` 參數傳給 `
    `。 -2. `Section` 把它的子元素包裹在 `` 中。 +2. `Section` 把它的 children 包裹在 `` 中。 3. `Heading` 使用 `useContext(LevelContext)` 訪問上層最近的 `LevelContext` 提供的值。 ## 在相同的组件中使用并提供 context {/*using-and-providing-context-from-the-same-component*/} @@ -585,7 +585,7 @@ export default function Page() { ... ``` -由於 context 讓你可以從上層的組件中讀取信息,每個 `Section` 都會從上層的 `Section` 讀取 `level`,並自動向下層傳遞 `level + 1`。你可以像下面這樣做。 +由於 context 讓你可以從上層的 component 中讀取信息,每個 `Section` 都會從上層的 `Section` 讀取 `level`,並自動向下層傳遞 `level + 1`。你可以像下面這樣做。 ```js src/Section.js {5,8} import { useContext } from 'react'; @@ -695,19 +695,19 @@ export const LevelContext = createContext(0); -現在,`Heading` 和 `Section` 都通過讀取 `LevelContext` 來判斷它們「深度」。並且 `Section` 把它的子組件都包裹在 `LevelContext` 中來指定其中的任何內容都處於一個「更深」的級別。 +現在,`Heading` 和 `Section` 都通過讀取 `LevelContext` 來判斷它們「深度」。並且 `Section` 把它的 child component 都包裹在 `LevelContext` 中來指定其中的任何內容都處於一個「更深」的級別。 -該示例使用標題級別來說明,是因為它們直觀地顯示了嵌套組件是如何覆蓋 context。但是 context 對於許多其他場景也很有用。你可以用它來傳遞整個字數需要的任何信息: 當前的顏色主題、當前的登錄用戶等。 +該示例使用標題級別來說明,是因為它們直觀地顯示了嵌套 component 是如何覆蓋 context。但是 context 對於許多其他場景也很有用。你可以用它來傳遞整個字數需要的任何信息: 當前的顏色主題、當前的登錄用戶等。 -## Context 能穿過中間層級的組件 {/*context-passes-through-intermediate-components*/} +## Context 能穿過中間層級的 component {/*context-passes-through-intermediate-components*/} -你可以在提供 context 的組件和使用它的組件之間的層級插入任意數量的組件。這包括像 `
    ` 這樣的內置組件和你自己創建的組件。 +你可以在提供 context 的 component 和使用它的 component 之間的層級插入任意數量的 component 。這包括像 `
    ` 這樣的內置 component 和你自己創建的 component 。 -該實例中,相同的 `Post` 組件(帶有虛線邊框)在兩個不同的嵌套層級上渲染。注意,它內部的 `` 會自動從最近的 `
    ` 獲取它的級別: +該實例中,相同的 `Post` component (帶有虛線邊框)在兩個不同的嵌套層級上渲染。注意,它內部的 `` 會自動從最近的 `
    ` 獲取它的級別: @@ -834,11 +834,11 @@ export const LevelContext = createContext(0); 你不需要做任何特殊的操作。`Section` 為它內部的樹指定了一個 context,所以你可以在任何地方插入一個 ``,並且它會有正確的尺寸。在上邊的 sandbox 嘗試下! -**Context 讓你可以編寫「適應周圍的環境」的組件,並且根據 _在哪_ (或者說,_在哪個 context 中_)來渲染它們不同的樣子。** +**Context 讓你可以編寫「適應周圍的環境」的 component,並且根據 _在哪_ (或者說,_在哪個 context 中_)來渲染它們不同的樣子。** -Context 的工作方式可能會讓你想起[CSS 屬性繼承](https://developer.mozilla.org/en-US/docs/Web/CSS/inheritance) 在 CSS, 你可以指定 `color: blue` 給一個 `
    `, 並且在其中的任意 DOM 節點,無論多深,都會繼承那個顏色,除非中間的其他 DOM 節點用 `color: green` 覆蓋它。類似地,在 React 中,覆蓋來自上層的某些 Context 的唯一方法時間子組件包裹到一個提供不同值的 context provider 中。 +Context 的工作方式可能會讓你想起[CSS 屬性繼承](https://developer.mozilla.org/en-US/docs/Web/CSS/inheritance) 在 CSS, 你可以指定 `color: blue` 給一個 `
    `, 並且在其中的任意 DOM node,無論多深,都會繼承那個顏色,除非中間的其他 DOM node用 `color: green` 覆蓋它。類似地,在 React 中,覆蓋來自上層的某些 Context 的唯一方法時間 child component 包裹到一個提供不同值的 context provider 中。 -在 CSS 中,不同屬性的 `color` 和 `background-color` 不會彼此覆蓋。你可以設置所有的 `
    ` 都為 `color` 紅色,還不會影響 `background-color`。類似地, **不同的 React context 不會彼此覆蓋。** 你用 `createContext()` 創建的每個 context 都和其他 context 完全分離,只有使用和提供 *那個特定的* context 才會聯繫到一起。一個組件可以毫無問題地使用或者是提供不同的 context。 +在 CSS 中,不同屬性的 `color` 和 `background-color` 不會彼此覆蓋。你可以設置所有的 `
    ` 都為 `color` 紅色,還不會影響 `background-color`。類似地, **不同的 React context 不會彼此覆蓋。** 你用 `createContext()` 創建的每個 context 都和其他 context 完全分離,只有使用和提供 *那個特定的* context 才會聯繫到一起。一個 component 可以毫無問題地使用或者是提供不同的 context。 ## 在你使用 context 之前 {/*before-you-use-context*/} @@ -846,31 +846,31 @@ Context 的工作方式可能會讓你想起[CSS 屬性繼承](https://developer 在你使用 context 之前,這裡有一些可供你選擇的代替方案: -1. **從 [傳遞 props 開始。](/learn/passing-props-to-a-component)** 如果你的組件看起來不起眼,那麼通過十幾個組件向下傳遞一堆 props 並不少見。這有點像是在埋頭苦幹,但是這樣做可以讓哪些組件用了哪些數據變得十分清晰!維護你代碼的人會很高興你用 props 傳遞數據,這會讓數據流變得更加清晰。 -2. **抽象組件並 [把 JSX 作為 `children`](/learn/passing-props-to-a-component#passing-jsx-as-children) 傳給它們。** 如果你通過很多層不使用該數據的中間組件(並且只會向下傳遞)來傳遞數據。舉個例子,你可能想把一些像 `posts` 的 props 數據傳遞到不會直接使用這個參數的組件,比如說 ``。更好的方式是,讓 `Layout` 把 `children` 當做一個參數,然後渲染 ``。這樣就減少了定義數據的組件和使用數據的組件之間的層級。 +1. **從 [傳遞 props 開始。](/learn/passing-props-to-a-component)** 如果你的 component 看起來不起眼,那麼通過十幾個 component 向下傳遞一堆 props 並不少見。這有點像是在埋頭苦幹,但是這樣做可以讓哪些 component 用了哪些數據變得十分清晰!維護你代碼的人會很高興你用 props 傳遞數據,這會讓數據流變得更加清晰。 +2. **抽象 component 並 [把 JSX 作為 `children`](/learn/passing-props-to-a-component#passing-jsx-as-children) 傳給它們。** 如果你通過很多層不使用該數據的中間 component (並且只會向下傳遞)來傳遞數據。舉個例子,你可能想把一些像 `posts` 的 props 數據傳遞到不會直接使用這個參數的 component,比如說 ``。更好的方式是,讓 `Layout` 把 `children` 當做一個參數,然後渲染 ``。這樣就減少了定義數據的 component 和使用數據的 component 之間的層級。 如果這兩種替代方案都不適合你,再考慮使用 context。 ## Context 的使用場景 {/*use-cases-for-context*/} -* **主題:** 如果你的應用允許用戶更改其外觀(例如 dark mode),你可以在應用頂層定義一個 context provider,並在需要調整其外觀的組件中使用該 context。 -* **當前賬戶:** 許多組件可能需要知道當前登錄的用戶信息。把它放到 context 中可以方便在樹中任何為止讀取它。某些應用還允許你同時操作多個賬戶(例如,以不同用戶的身份發表評論)。在這些情況下,將 UI 的一部分包裹到具有不同賬戶數據的 provider 中會很方便。 +* **主題:** 如果你的應用允許用戶更改其外觀(例如 dark mode),你可以在應用頂層定義一個 context provider,並在需要調整其外觀的 component 中使用該 context。 +* **當前賬戶:** 許多 component 可能需要知道當前登錄的用戶信息。把它放到 context 中可以方便在樹中任何為止讀取它。某些應用還允許你同時操作多個賬戶(例如,以不同用戶的身份發表評論)。在這些情況下,將 UI 的一部分包裹到具有不同賬戶數據的 provider 中會很方便。 * **路由:** 大多數路由解決方案在其內部使用 context 來保存當前路由。這就是每個鏈接「知道」它是否處於活動狀態的方式。如果你創建自己的路由,你可能也會這麼做。 -* **狀態管理:** 隨著你的應用的開發,最終在靠近應用頂部的為止可能會有很多 state。許多遙遠的下層組件可能想要修改它們。通常 [將 reducer 與 context 搭配使用](/learn/scaling-up-with-reducer-and-context) 來管理複雜的狀態並將其傳遞給深層的組件來避免過多的麻煩。 +* **state 管理:** 隨著你的應用的開發,最終在靠近應用頂部的為止可能會有很多 state。許多遙遠的下層 component 可能想要修改它們。通常 [將 reducer 與 context 搭配使用](/learn/scaling-up-with-reducer-and-context) 來管理複雜的 state 並將其傳遞給深層的 component 來避免過多的麻煩。 -Context 不局限於靜態值。如果你在下一次渲染時傳遞不同的值,React 將會更新所有讀取它的下層組件!這就是 context 經常和 state 結合使用的原因。 +Context 不局限於靜態值。如果你在下一次渲染時傳遞不同的值,React 將會更新所有讀取它的下層 component !這就是 context 經常和 state 結合使用的原因。 -通常來說,如果樹中不同部分的遠距離組件需要傳遞某些信息,使用 context 是一個很好的選擇。 +通常來說,如果樹中不同部分的遠距離 component 需要傳遞某些信息,使用 context 是一個很好的選擇。 -* Context 讓一個組件向其下整個樹的組件提供信息。 +* Context 讓一個 component 向其下整個樹的 component 提供信息。 * 傳遞 context 的步驟: - 1. 通過 `export const MyContext = createContext(defaultValue)` 創建並導出 context。 - 2. 在無論層級多深的任何子組件中,把 context 傳遞給 `useContext(MyContext)` Hook 來讀取它。 - 3. 在父組件中把 children 包裹在 `` 中來提供 context。 -* Context 會穿過在中間的所有組件。 -* Context 讓你寫出「適應周圍環境的」組件。 + 1. 通過 `export const MyContext = createContext(defaultValue)` 創建並 export context。 + 2. 在無論層級多深的任何 child component 中,把 context 傳遞給 `useContext(MyContext)` Hook 來讀取它。 + 3. 在 parent 中把 children 包裹在 `` 中來提供 context。 +* Context 會穿過在中間的所有 component。 +* Context 讓你寫出「適應周圍環境的」 component。 * 在你使用 context 之前,先嘗試下傳遞 props 或者將 JSX 作為 `children` 傳遞。 @@ -879,9 +879,9 @@ Context 不局限於靜態值。如果你在下一次渲染時傳遞不同的值 #### 用 context 代替 prop 逐級傳遞 {/*replace-prop-drilling-with-context*/} -該示例中,切換復選框狀態會修改傳入的每個 `` 的 `imageSize` 參數。復選框的 state 保存在頂層的 `App` 組件中,但每個 `` 都需要注意它。 +該示例中,切換復選框狀態會修改傳入的每個 `` 的 `imageSize` 參數。復選框的 state 保存在頂層的 `App` component 中,但每個 `` 都需要注意它。 -现在, `App` 将 `imageSize` 傳遞給 `List`,再將其傳遞給每個 `Place`,`Place` 又將其傳遞給 `PlaceImage`。刪除 `imageSize` 參數,然後在 `App` 組件中直接將其傳遞給 `PlaceImage`。 +现在, `App` 将 `imageSize` 傳遞給 `List`,再將其傳遞給每個 `Place`,`Place` 又將其傳遞給 `PlaceImage`。刪除 `imageSize` 參數,然後在 `App` component 中直接將其傳遞給 `PlaceImage`。 你可以在 `Context.js` 中聲明 context。 @@ -1157,7 +1157,7 @@ li { -注意,中間的組件不再需要傳遞 `imageSize`。 +注意,中間的 component 不再需要傳遞 `imageSize`。