Skip to content

Commit

Permalink
docs: 更新 Context 文档
Browse files Browse the repository at this point in the history
  • Loading branch information
yuche committed May 24, 2019
1 parent 6c7737d commit 9161be3
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
* [条件渲染](condition.md)
* [列表渲染](list.md)
* [函数式组件](functional-component.md)
* [Context](context.md)
* [Hooks](hooks.md)
* [Refs 引用](ref.md)
* [消息机制](events.md)
Expand Down
176 changes: 176 additions & 0 deletions docs/context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
---
title: Context
---

> `v1.3.0-beta.5` 起支持
> 在 Taro 中没有对 React 15 的 [legacy context](https://zh-hans.reactjs.org/docs/legacy-context.html) 进行支持,无法使用 `getChildContext()` API。
在一个典型的 Taro 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但这种做法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。


## API

### Taro.createContext

```jsx
const MyContext = Taro.createContext(defaultValue)
```
创建一个 Context 对象。当 Taro 渲染一个订阅了这个 Context 对象的组件,这个组件会从最先渲染的 `Provider` 中读取到 `Provider``value`

> 在 Taro 中,即便在框架层面也无法知道组件的树结构,因此 Taro 无法像 React 一样往父组件找离自己最近的 Provider。因此创建的 Context 最好只在一个地方使用。

### Context.Provider

```jsx
<MyContext.Provider value={/* 某个值 */}>
```

每个 Context 对象都会返回一个 Provider Taro 组件,它允许消费组件订阅 context 的变化。

Provider 接收一个 `value` 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。

当 Provider 的 `value` 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部包含 `contextType` 或使用 `useContext` 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。

通过新旧值检测来确定变化,使用了与 `Object.is` 相同的算法。

> 由于现在 Taro 还没有 render props 的完整支持,所以无法使用 Context.Comsumer API,如果要消费 Context,可以使用 `ContextType``useContext` API。
### Class.contextType

```jsx
class MyClass extends Taro.Component {
componentDidMount() {
let value = this.context;
/* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* 基于 MyContext 组件的值进行渲染 */
}
}
MyClass.contextType = MyContext;
```

挂载在 class 上的 `contextType` 属性会被重赋值为一个由 `Taro.createContext()` 创建的 Context 对象。这能让你使用 this.context 来消费 Context 上的那个值。你可以在任何生命周期中访问到它,包括 render 函数中。

> 注意:
> 你只通过该 API 订阅单一 context。如果你想订阅多个,阅读使用多个 Context 章节
> 如果你正在使用实验性的 public class fields 语法,你可以使用 static 这个类属性来初始化你的 contextType。
```jsx
class MyClass extends React.Component {
static contextType = MyContext;
render() {
let value = this.context;
/* 基于这个值进行渲染工作 */
}
}
```

## 示例

### 动态 Context

```jsx
// counter-context.js
export const CounterContext = React.createContext(0);

// index.js
class Index extends Component {
render () {
const [ count, setCount ] = useState(0)
return (
<CounterContext.Provider value={count}>
<View className='container'>
<Test />
<Button onClick={() => setCount(0)}>Reset</Button>
<Button onClick={() => setCount(prevCount => prevCount + 1)}>+</Button>
<Button onClick={() => setCount(prevCount => prevCount - 1)}>-</Button>
</View>
</CounterContext.Provider>
)
}
}

// child.js
class Child extends Taro.Component {
shouldComponentUpdate () {
// 即便返回 false 也不会阻止 CounterContext 更新消费它的组件
return false
}

render () {
return <Counter />
}
}

// counter.js
import { CounterContext } from './counter-context.js'
class Counter extends Taro.Component {
static contextType = CounterContext

render () {
const value = this.context
return (
<View>
Count: {value}
</View>
)
}
}
```

我们在这个例子中把计数器 `count` 的值通过 `CounterContext.Provider` 往下传递,`Child` 组件中虽然禁止了更新,但 `Counter` 组件在 `CounterContext.Provider``value` 每次变化之后,还是能够订阅更新,收到每次 `count` 的值。

### 消费多个 Context

```jsx
const ThemeContext = Taro.createContext('light');

// 用户登录 context
const UserContext = Taro.createContext({
name: 'Guest',
});

class App extends Taro.Component {
render() {
const {signedInUser, theme} = this.props;

// 提供初始 context 值的 App 组件
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={signedInUser}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
}

function Layout() {
return (
<div>
<Sidebar />
<Content />
</div>
);
}

// 一个组件可能会消费多个 context
function Content() {
const theme = useContext(ThemeContext)
const user = useContext(UserContext)
return (
<ProfilePage user={user} theme={theme} />
)
}
```
21 changes: 21 additions & 0 deletions docs/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,3 +340,24 @@ function TextInputWithFocusButton() {
>
> 如果你正在将代码从 class 组件迁移到使用 Hook 的函数组件,则需要注意 `useLayoutEffect``componentDidMount``componentDidUpdate` 的调用阶段是一样的。但是,我们推荐你**一开始先用 `useEffect`**,只有当它出问题的时再尝试使用 `useLayoutEffect`

### `useContext`

```jsx
const value = useContext(MyContext)
```

接收一个 context (`Taro.createContext` 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中最先渲染的 `<MyContext.Provider value={value}>``value`决定。

当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context `value` 值。

别忘记 `useContext` 的参数必须是 context 对象本身:

正确: `useContext(MyContext)`
错误: `useContext(MyContext.Consumer)`
错误: `useContext(MyContext.Provider)`
调用了 `useContext` 的组件总会在 context 值变化时重新渲染。

> 如果你在接触 Hook 前已经对 context API 比较熟悉,那应该可以理解,`useContext(MyContext)` 相当于 class 组件中的 `static contextType = MyContext` 或者 <MyContext.Consumer>。
> `useContext(MyContext)` 只是让你能够读取 context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context。
1 change: 1 addition & 0 deletions website/sidebars.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"condition",
"list",
"functional-component",
"context",
"hooks",
"children",
"ref",
Expand Down

0 comments on commit 9161be3

Please sign in to comment.