From e8b920c049e6d9096e9b78675700e626fb39bfb1 Mon Sep 17 00:00:00 2001 From: betamee Date: Mon, 11 Feb 2019 19:45:10 +0800 Subject: [PATCH 01/48] finish context --- content/docs/context.md | 134 +++++++++--------- examples/context/motivation-problem.js | 9 +- examples/context/motivation-solution.js | 24 ++-- examples/context/multiple-contexts.js | 8 +- examples/context/theme-detailed-app.js | 9 +- .../context/theme-detailed-theme-context.js | 2 +- .../context/updating-nested-context-app.js | 7 +- .../updating-nested-context-context.js | 3 +- ...ing-nested-context-theme-toggler-button.js | 5 +- 9 files changed, 97 insertions(+), 104 deletions(-) diff --git a/content/docs/context.md b/content/docs/context.md index 8e41a465f9..20ca7ee59a 100644 --- a/content/docs/context.md +++ b/content/docs/context.md @@ -4,57 +4,57 @@ title: Context permalink: docs/context.html --- -Context provides a way to pass data through the component tree without having to pass props down manually at every level. +Context 提供了一个无需手动层层传递 props 属性就能在组件树间传递数据的方法。 -In a typical React application, data is passed top-down (parent to child) via props, but this can be cumbersome for certain types of props (e.g. locale preference, UI theme) that are required by many components within an application. Context provides a way to share values like these between components without having to explicitly pass a prop through every level of the tree. +在一个典型的 React 应用中,数据是通过 props 属性由上向下(由父及子)进行传递的,但这种做法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的每个层级传递 props 。 -- [When to Use Context](#when-to-use-context) -- [Before You Use Context](#before-you-use-context) +- [何时使用 Context](#when-to-use-context) +- [使用 Context 之前的考虑](#before-you-use-context) - [API](#api) - [React.createContext](#reactcreatecontext) - [Context.Provider](#contextprovider) - [Class.contextType](#classcontexttype) - [Context.Consumer](#contextconsumer) -- [Examples](#examples) - - [Dynamic Context](#dynamic-context) - - [Updating Context from a Nested Component](#updating-context-from-a-nested-component) - - [Consuming Multiple Contexts](#consuming-multiple-contexts) -- [Caveats](#caveats) -- [Legacy API](#legacy-api) +- [案例](#examples) + - [动态 Context](#dynamic-context) + - [在嵌套组件中更新 Context](#updating-context-from-a-nested-component) + - [使用多个 Context](#consuming-multiple-contexts) +- [注意事项](#caveats) +- [废弃的 API](#legacy-api) -## When to Use Context {#when-to-use-context} +## 何时使用 Context {#when-to-use-context} -Context is designed to share data that can be considered "global" for a tree of React components, such as the current authenticated user, theme, or preferred language. For example, in the code below we manually thread through a "theme" prop in order to style the Button component: +Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。举个例子,在下面的代码中,我们通过一个“theme”属性手动调整一个按钮组件的样式: `embed:context/motivation-problem.js` -Using context, we can avoid passing props through intermediate elements: +使用 context, 我们可以避免通过中间元素传递 props: `embed:context/motivation-solution.js` -## Before You Use Context {#before-you-use-context} +## 使用 Context 之前的考虑 {#before-you-use-context} -Context is primarily used when some data needs to be accessible by *many* components at different nesting levels. Apply it sparingly because it makes component reuse more difficult. +Context 主要应用场景在于*很多*不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。 -**If you only want to avoid passing some props through many levels, [component composition](/docs/composition-vs-inheritance.html) is often a simpler solution than context.** +**如果你只是想避免层层传递一些属性,[组件组合(component composition)](/docs/composition-vs-inheritance.html)有时候是一个比 context 更好的解决方案。** -For example, consider a `Page` component that passes a `user` and `avatarSize` prop several levels down so that deeply nested `Link` and `Avatar` components can read it: +比如,考虑这样一个 `Page` 组件,它层层传递 `user` 和 `avatarSize` 属性,从而深度嵌套的 `Link` 和 `Avatar` 组件可以读取到这些属性: ```js -// ... which renders ... +// ... 渲染出 ... -// ... which renders ... +// ... 渲染出 ... -// ... which renders ... +// ... 渲染出 ... ``` -It might feel redundant to pass down the `user` and `avatarSize` props through many levels if in the end only the `Avatar` component really needs it. It's also annoying that whenever the `Avatar` component needs more props from the top, you have to add them at all the intermediate levels too. +如果在最后只有 `Avatar` 组件真的需要值,那么层层传递 `user` 和 `avatarSize` 就显得非常冗余。而且一旦 `Avatar` 组件需要更多从顶部下来的 props,你还得在中间层级一个一个加上去,这将会变得非常恼火。 -One way to solve this issue **without context** is to [pass down the `Avatar` component itself](/docs/composition-vs-inheritance.html#containment) so that the intermediate components don't need to know about the `user` prop: +一种**无需 context** 的解决方案是[将 `Avatar` 组件自身传递下去](/docs/composition-vs-inheritance.html#containment),因而中间组件无需知道 `user` 属性: ```js function Page(props) { @@ -67,21 +67,21 @@ function Page(props) { return ; } -// Now, we have: +// 现在,我们有这样的组件: -// ... which renders ... +// ... 渲染出 ... -// ... which renders ... +// ... 渲染出 ... -// ... which renders ... +// ... 渲染出 ... {props.userLink} ``` -With this change, only the top-most Page component needs to know about the `Link` and `Avatar` components' use of `user` and `avatarSize`. +这种变化下,只有最顶部的 Page 组件需要知道 `Link` 和 `Avatar` 组件是如何使用 `user` 和 `avatarSize` 的。 -This *inversion of control* can make your code cleaner in many cases by reducing the amount of props you need to pass through your application and giving more control to the root components. However, this isn't the right choice in every case: moving more complexity higher in the tree makes those higher-level components more complicated and forces the lower-level components to be more flexible than you may want. +这种对组件的*反转控制*减少了在你的应用中要传递的 props 数量,这在很多场景下会使得你的代码更加干净,使你对根组件有更多的把控。但是,这并不适用于每一个场景:这种将逻辑提升到组件树的更高层次来处理,会使得这些高层组件变得更复杂,并且会强行将低层组件适应这样的形式,这可能不会是你想要的。 -You're not limited to a single child for a component. You may pass multiple children, or even have multiple separate "slots" for children, [as documented here](/docs/composition-vs-inheritance.html#containment): +而且你的组件并不限制于接收单个子组件(child)。你可能会传递多个子组件(children),甚至会为这些子组件(children)封装多个单独的“接口(slots)”,[正如这里的文档所列举的](/docs/composition-vs-inheritance.html#containment) ```js function Page(props) { @@ -103,9 +103,9 @@ function Page(props) { } ``` -This pattern is sufficient for many cases when you need to decouple a child from its immediate parents. You can take it even further with [render props](/docs/render-props.html) if the child needs to communicate with the parent before rendering. +这种模式足够覆盖很多场景了,在这些场景下你需要将子组件(child)和直接关联的父组件(immediate parents)解耦。如果子组件(child)需要在渲染前和父组件进行一些交流,你可以进一步使用 [render props](/docs/render-props.html)。 -However, sometimes the same data needs to be accessible by many components in the tree, and at different nesting levels. Context lets you "broadcast" such data, and changes to it, to all components below. Common examples where using context might be simpler than the alternatives include managing the current locale, theme, or a data cache. +但是,有的时候在组件树中很多不同层级的组件需要访问同样的一批数据。Context 能让你将这些数据在组件树中“广泛传播(broadcast)”,所有的组件都能访问到这些数据,也能访问到后续的数据更新。使用 context 的通用的场景包括管理当前的 locale,theme,或者一些缓存数据,这比替代方案要简单的多。 ## API {#api} @@ -115,27 +115,27 @@ However, sometimes the same data needs to be accessible by many components in th const MyContext = React.createContext(defaultValue); ``` -Creates a Context object. When React renders a component that subscribes to this Context object it will read the current context value from the closest matching `Provider` above it in the tree. +创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件(consumer),这个组件会从组件树中离自身最近的那个匹配的 `Provider` 中读取到当前的 context 值。 -The `defaultValue` argument is **only** used when a component does not have a matching Provider above it in the tree. This can be helpful for testing components in isolation without wrapping them. Note: passing `undefined` as a Provider value does not cause consuming components to use `defaultValue`. +`defaultValue` 参数**只**在组件没有在上层组件树中找到匹配的 Provider 情况下会有用。这有助于在不封装它们的情况下对组件进行测试。注意:传递 `undefined` 作为 Provider 值的情况下,调用组件(consumer)不会使用 `defaultValue` 值。 ### `Context.Provider` {#contextprovider} ```js - + ``` -Every Context object comes with a Provider React component that allows consuming components to subscribe to context changes. +每一个 Context 对象都会返回一个 Provider React 组件,它允许要调用的组件(consumer)订阅 context 的变化。 -Accepts a `value` prop to be passed to consuming components that are descendants of this Provider. One Provider can be connected to many consumers. Providers can be nested to override values deeper within the tree. +Provider 接收一个 `value` 属性,传递给下面的调用组件(consumer)。一个 Provider 可以和多个调用组件有联系。多个 Provider 也可以嵌套使用,深层的会覆盖上层的数据。 -All consumers that are descendants of a Provider will re-render whenever the Provider's `value` prop changes. The propagation from Provider to its descendant consumers is not subject to the `shouldComponentUpdate` method, so the consumer is updated even when an ancestor component bails out of the update. +当 Provider 的 `value` 值发生变化时,它下面的所有调用组件(consumer)都会重新渲染。从 Provider 到它下面的调用组件不会受制于 `shouldComponentUpdate` 函数,因此调用组件在其上层组件没有触发更新的情况下也能更新。 -Changes are determined by comparing the new and old values using the same algorithm as [`Object.is`](//developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#Description). +这些变化会通过比较新旧值而被检测到,这里使用的算法是 [`Object.is`](//developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#Description)。 -> Note +> 注意 > -> The way changes are determined can cause some issues when passing objects as `value`: see [Caveats](#caveats). +> 当传递对象给 `value` 时,变化被检测的方式会导致一些问题: 详见 [注意事项](#caveats). ### `Class.contextType` {#classcontexttype} @@ -143,7 +143,7 @@ Changes are determined by comparing the new and old values using the same algori class MyClass extends React.Component { componentDidMount() { let value = this.context; - /* perform a side-effect at mount using the value of MyContext */ + /* 在组件挂载完成后使用 MyContext 的值来执行一些有副作用的操作 */ } componentDidUpdate() { let value = this.context; @@ -155,19 +155,19 @@ class MyClass extends React.Component { } render() { let value = this.context; - /* render something based on the value of MyContext */ + /* 基于 MyContext 的值进行渲染 */ } } MyClass.contextType = MyContext; ``` -The `contextType` property on a class can be assigned a Context object created by [`React.createContext()`](#reactcreatecontext). This lets you consume the nearest current value of that Context type using `this.context`. You can reference this in any of the lifecycle methods including the render function. +挂在类(class)上的 `contextType` 属性会被重赋值为一个由 [`React.createContext()`](#reactcreatecontext) 创建的 Context 对象。这能让你使用 `this.context` 来获取最近 Context 上的那个值。你可以在任何生命周期中访问到它,包括 render 函数。 -> Note: +> 注意: > -> You can only subscribe to a single context using this API. If you need to read more than one see [Consuming Multiple Contexts](#consuming-multiple-contexts). +> 你只可以使用这个 API 订阅单一 context。但如果你想订阅不只一个,阅读[使用多个 Context](#consuming-multiple-contexts) 章节 > -> If you are using the experimental [public class fields syntax](https://babeljs.io/docs/plugins/transform-class-properties/), you can use a **static** class field to initialize your `contextType`. +> 如果你正在使用实验性的 [public class fields 语法](https://babeljs.io/docs/plugins/transform-class-properties/),你可以使用 `static` 这个类属性来初始化你的 `contextType`。 ```js @@ -175,7 +175,7 @@ class MyClass extends React.Component { static contextType = MyContext; render() { let value = this.context; - /* render something based on the value */ + /* 基于这个值进行渲染工作 */ } } ``` @@ -184,23 +184,23 @@ class MyClass extends React.Component { ```js - {value => /* render something based on the context value */} + {value => /* 基于 context 值进行渲染*/} ``` -A React component that subscribes to context changes. This lets you subscribe to a context within a [function component](/docs/components-and-props.html#function-and-class-components). +这里,React 组件也可以订阅到 context 变更。这能让你在 [函数式组件](/docs/components-and-props.html#function-and-class-components) 中完成订阅 context。 -Requires a [function as a child](/docs/render-props.html#using-props-other-than-render). The function receives the current context value and returns a React node. The `value` argument passed to the function will be equal to the `value` prop of the closest Provider for this context above in the tree. If there is no Provider for this context above, the `value` argument will be equal to the `defaultValue` that was passed to `createContext()`. +这需要[函数作为子元素(function as a child)](/docs/render-props.html#using-props-other-than-render)这种做法。这个函数接收当前的 context 值,返回一个 React 节点。传递给函数的 `value` 值等同于往上组件树离这个 context 最近的 Provider 提供的 `value` 值。如果没有对应的 Provider,`value` 参数等同于传递给 `createContext()` 的 `defaultValue`。 -> Note +> 注意 > -> For more information about the 'function as a child' pattern, see [render props](/docs/render-props.html). +> 想要了解更多关于'函数作为子元素(function as a child)'模式,见 [render props](/docs/render-props.html)。 -## Examples {#examples} +## 案例 {#examples} -### Dynamic Context {#dynamic-context} +### 动态 Context {#dynamic-context} -A more complex example with dynamic values for the theme: +对于上面的 theme 例子,使用动态值(dynamic values)后更复杂的用法: **theme-context.js** `embed:context/theme-detailed-theme-context.js` @@ -211,9 +211,9 @@ A more complex example with dynamic values for the theme: **app.js** `embed:context/theme-detailed-app.js` -### Updating Context from a Nested Component {#updating-context-from-a-nested-component} +### 在嵌套组件中更新 Context {#updating-context-from-a-nested-component} -It is often necessary to update the context from a component that is nested somewhere deeply in the component tree. In this case you can pass a function down through the context to allow consumers to update the context: +从一个在组件树中嵌套很深的组件中更新 context 是很有必要的。在这种场景下,你可以通过 context 传递一个函数,来允许调用组件更新 context: **theme-context.js** `embed:context/updating-nested-context-context.js` @@ -224,28 +224,28 @@ It is often necessary to update the context from a component that is nested some **app.js** `embed:context/updating-nested-context-app.js` -### Consuming Multiple Contexts {#consuming-multiple-contexts} +### 调用多个 Context {#consuming-multiple-contexts} -To keep context re-rendering fast, React needs to make each context consumer a separate node in the tree. +为了确保 context 快速进行重渲染,React 需要使每一个 context 调用组件在组件树中成为一个单独的节点。 `embed:context/multiple-contexts.js` -If two or more context values are often used together, you might want to consider creating your own render prop component that provides both. +如果两个或者更多的 context 值经常被一起使用,那你可能要考虑一下另外创建你自己的渲染组件,以提供这些值。 -## Caveats {#caveats} +## 注意事项 {#caveats} -Because context uses reference identity to determine when to re-render, there are some gotchas that could trigger unintentional renders in consumers when a provider's parent re-renders. For example, the code below will re-render all consumers every time the Provider re-renders because a new object is always created for `value`: +因为 context 会使用参考标识(reference identity)来决定何时进行渲染,这里可能会有一些陷进,当 provider 的父组件进行重渲染时,可能会在调用组件(consumers)中触发意外的渲染。举个例子,当每一次 Provider 重渲染时,以下的代码会重渲染所有下面的调用组件,因为 `value` 属性总是被赋值为新的对象: `embed:context/reference-caveats-problem.js` -To get around this, lift the value into the parent's state: +为了防止这种情况, 将 value 提升到父节点的 state里: `embed:context/reference-caveats-solution.js` -## Legacy API {#legacy-api} +## 废弃 API {#legacy-api} + +> 注意 +> 先前 React 使用实验性的 context API运行,旧的API将会在所有16.x版本中得到支持,但用到它的应用应该迁移到新版本。废弃的API将在未来的 React 版本中被移除。阅读 [废弃的 context 文档](/docs/legacy-context.html) 了解更多。 -> Note -> -> React previously shipped with an experimental context API. The old API will be supported in all 16.x releases, but applications using it should migrate to the new version. The legacy API will be removed in a future major React version. Read the [legacy context docs here](/docs/legacy-context.html). diff --git a/examples/context/motivation-problem.js b/examples/context/motivation-problem.js index d166f400a0..33eabdbc74 100644 --- a/examples/context/motivation-problem.js +++ b/examples/context/motivation-problem.js @@ -5,11 +5,10 @@ class App extends React.Component { } function Toolbar(props) { - // highlight-range{1-4,7} - // The Toolbar component must take an extra "theme" prop - // and pass it to the ThemedButton. This can become painful - // if every single button in the app needs to know the theme - // because it would have to be passed through all components. + // highlight-range{1-3,6} + // Toolbar 组件接受一个额外的“theme”属性,然后传递给 ThemedButton 组件。 + // 如果应用中每一个单独的按钮都需要知道 theme 的值,这会是件很麻烦的事, + // 因为必须将这个值层层传递所有组件。 return (
diff --git a/examples/context/motivation-solution.js b/examples/context/motivation-solution.js index 94c6030a98..32b2d9fd8d 100644 --- a/examples/context/motivation-solution.js +++ b/examples/context/motivation-solution.js @@ -1,15 +1,14 @@ -// highlight-range{1-4} -// Context lets us pass a value deep into the component tree -// without explicitly threading it through every component. -// Create a context for the current theme (with "light" as the default). +// highlight-range{1-3} +// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。 +// 为当前的 theme 创建一个 context(“light”为默认值)。 const ThemeContext = React.createContext('light'); class App extends React.Component { render() { // highlight-range{1-3,5} - // Use a Provider to pass the current theme to the tree below. - // Any component can read it, no matter how deep it is. - // In this example, we're passing "dark" as the current value. + // 使用一个 Provider 来将当前的 theme 传递给以下的组件树。 + // 无论多深,任何组件都能读取这个值。 + // 在这个例子中,我们将“dark”作为当前的值传递下去。 return ( @@ -18,9 +17,8 @@ class App extends React.Component { } } -// highlight-range{1,2} -// A component in the middle doesn't have to -// pass the theme down explicitly anymore. +// highlight-range{1} +// 中间的组件再也不必指明往下传递 theme 了。 function Toolbar(props) { return (
@@ -31,9 +29,9 @@ function Toolbar(props) { class ThemedButton extends React.Component { // highlight-range{1-3,6} - // Assign a contextType to read the current theme context. - // React will find the closest theme Provider above and use its value. - // In this example, the current theme is "dark". + // 指定 contextType 读取当前的 theme context。 + // React 会往上找到最近的 theme Provider,然后使用它的值。 + // 在这个例子中,当前的 theme 值为“dark”。 static contextType = ThemeContext; render() { return