Context

v1.3.0-beta.5 起支持在 Taro 中没有对 React 15 的 legacy context 进行支持,无法使用 getChildContext() API。

在一个典型的 Taro 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但这种做法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。

API

Taro.createContext

  1. constMyContext=Taro.createContext(defaultValue)

创建一个 Context 对象。当 Taro 渲染一个订阅了这个 Context 对象的组件,这个组件会从最先渲染的 Provider 中读取到 Providervalue

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

Context.Provider

  1. <MyContext.Providervalue={/* 某个值 */}>

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

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

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

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

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

Class.contextType

  1. classMyClassextendsTaro.Component{
  2. componentDidMount(){
  3. let value =this.context;
  4. /* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */
  5. }
  6. componentDidUpdate(){
  7. let value =this.context;
  8. /* ... */
  9. }
  10. componentWillUnmount(){
  11. let value =this.context;
  12. /* ... */
  13. }
  14. render(){
  15. let value =this.context;
  16. /* 基于 MyContext 组件的值进行渲染 */
  17. }
  18. }
  19. MyClass.contextType =MyContext;

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

注意:你只通过该 API 订阅单一 context。如果你想订阅多个,阅读使用多个 Context 章节如果你正在使用实验性的 public class fields 语法,你可以使用 static 这个类属性来初始化你的 contextType。

  1. classMyClassextendsReact.Component{
  2. static contextType =MyContext;
  3. render(){
  4. let value =this.context;
  5. /* 基于这个值进行渲染工作 */
  6. }
  7. }

示例

动态 Context

  1. // counter-context.js
  2. exportconstCounterContext=Taro.createContext(0);
  3. // index.js
  4. classIndexextendsComponent{
  5. render (){
  6. const[ count, setCount ]= useState(0)
  7. return(
  8. <CounterContext.Provider value={count}>
  9. <View className='container'>
  10. <Test/>
  11. <Button onClick={()=> setCount(0)}>Reset</Button>
  12. <Button onClick={()=> setCount(prevCount => prevCount +1)}>+</Button>
  13. <Button onClick={()=> setCount(prevCount => prevCount -1)}>-</Button>
  14. </View>
  15. </CounterContext.Provider>
  16. )
  17. }
  18. }
  19. // child.js
  20. classChildextendsTaro.Component{
  21. shouldComponentUpdate (){
  22. // 即便返回 false 也不会阻止 CounterContext 更新消费它的组件
  23. returnfalse
  24. }
  25. render (){
  26. return<Counter/>
  27. }
  28. }
  29. // counter.js
  30. import{CounterContext}from'./counter-context.js'
  31. classCounterextendsTaro.Component{
  32. static contextType =CounterContext
  33. render (){
  34. const value =this.context
  35. return(
  36. <View>
  37. Count:{value}
  38. </View>
  39. )
  40. }
  41. }

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

消费多个 Context

  1. constThemeContext=Taro.createContext('light');
  2. // 用户登录 context
  3. constUserContext=Taro.createContext({
  4. name:'Guest',
  5. });
  6. classAppextendsTaro.Component{
  7. render(){
  8. const{signedInUser, theme}=this.props;
  9. // 提供初始 context 值的 App 组件
  10. return(
  11. <ThemeContext.Provider value={theme}>
  12. <UserContext.Provider value={signedInUser}>
  13. <Layout/>
  14. </UserContext.Provider>
  15. </ThemeContext.Provider>
  16. );
  17. }
  18. }
  19. functionLayout(){
  20. return(
  21. <div>
  22. <Sidebar/>
  23. <Content/>
  24. </div>
  25. );
  26. }
  27. // 一个组件可能会消费多个 context
  28. functionContent(){
  29. const theme = useContext(ThemeContext)
  30. const user = useContext(UserContext)
  31. return(
  32. <ProfilePage user={user} theme={theme}/>
  33. )
  34. }