内容简介:在平时使用react的过程中,数据都是自顶而下的传递方式,例如,如果在顶层组件的state存储了theme主题相关的数据作为整个App的主题管理。那么正因为有了跨越层级传递值的这么一种需求,其实官方也提供了context的机制。通过context,我们就能够在子组件里获取祖先组件里的值,而不需要层层传递。其实很多的状态管理框架与react结合的库就是使用了context特性,例如著名的react-redux。在v16.3之前的context只是官方的实验性API,其实官方是不推荐开发者使用的,但是架不住很多
在平时使用react的过程中,数据都是自顶而下的传递方式,例如,如果在顶层组件的state存储了theme主题相关的数据作为整个App的主题管理。那么 在不借助任何第三方的状态管理框架 的情况下,想要在子组件里获取theme数据,就必须的一层层传递下去,即使两者之间的组件根本不需要该数据;就如同下图所示,并且如果App的层级越深,这之间的层层传递对开发者来说可谓是灾难。
引入context
正因为有了跨越层级传递值的这么一种需求,其实官方也提供了context的机制。通过context,我们就能够在子组件里获取祖先组件里的值,而不需要层层传递。其实很多的状态管理框架与react结合的库就是使用了context特性,例如著名的react-redux。
在v16.3之前的context只是官方的实验性API,其实官方是不推荐开发者使用的,但是架不住很多框架依旧在使用它;所以官方在v16.3发布的新的context API,新的API会更加的易用,本文也是以v16.3为准。
在新的context API中,React提供了一个createContext的方法,该方法返回一个包含了Provider,Consumer的对象,而Provoider,Consumer对象就是新API的重点。
我们先看一个简单的例子,再来讲解API。在本案例中,在顶层父组件的state存储着控制这个App的theme的一些属性,使用context来跨组件传递这些属性,使得底层组件能够直接得到这些属性。
首先在themeContext.js文件中定义context,并导出Provider以及Consumer:
import {createContext} from "react"; export const {Provider, Consumer} = createContext({ color: "green", fontSize: "20px" }); 复制代码
createContext需要传递一个参数,叫做defaultValue。这个值会在什么时候起作用呢?这个稍后解释。
然后我们就可以直接在顶层的App组件中,直接使用Provider:
import React, {Component} from 'react'; import {Provider} from "./context/themeContext"; import Parent from "./Parent"; class App extends Component { state = { color: "red", fontSize: "16px" }; render() { return ( <div className="App"> <Provider value={this.state}> <Parent/> </Provider> </div> ); } } export default App; 复制代码
我们直接在顶层的组件里使用Provider组件,并且Provider组件有一个value属性用于传递context的实际的value。然后我们就可以在底层的Child组件中得到这些value来使用。
层级关系:App -> Parent -> Child
import React, {PureComponent} from "react"; import {Consumer} from "./context/themeContext"; class Child extends PureComponent { render() { return <Consumer> { style => <div style={style}>This is Child Component that gets style value through context.</div> } </Consumer> } } export default Child; 复制代码
在Child组件中使用Consumer,就能够得到上层所传递context的值;Consumer的需要一个函数作为子元素,该函数的参数就是上层所传递context value,然后就可以返回该组件具体的组件样式了。
这就是一个简单的使用context的例子,可以看到context的API是非常简单的,也可容易使用,再简单总结一下API:
- createContext:用于创建context,需要一个defaultValue的参数,并返回一个包含Provider,以及Consumer的对象
- Provider:顶层用于提供context的组件,包含一个value的props,value是实际的context数据
- Consumer:底层用于获取context的组件,需要一个函数作为其子元素,该函数包含一个value的参数,该函数的参数就是上层所传递context value
看到这里,你可能会有一个疑惑:为什么createContext需要一个defaultValue,而Provider还需要一个实际的value?到底defaultValue是什么时候起作用呢?先抛出结论: 只有在上层组件没有提供Provider组件时,下层组件的Consumer才会直接使用defaultValue作为子函数的参数传递 。以本例子为例,只有在App组件压根没有使用Provider组件时,Child组件中的Consumer的子函数参数才会是 { color: "red", fontSize: "16px" }
这个defaultValue,其他情况都不会使用到这个值。这个地方有一个常见的误解:就是不给上层组件的Provider的value属性,或者让 value={undefined}
时,就会使用defaultValue,这是不对的!!!请切记,大家也可以自己尝试,看看是不是这个结论。
更近一步
虽然使用了Consumer能够让我们很方便的得到context的value,但是如果很多子元素要得到context的值,都去先调用Consumer,再在它的子函数里返回真正的组件内容,会显得十分的累赘。所以我们可以对Consumer进行一个简单的封装,封装一个connect的方法。去实现类似于react-redux其中的connect函数的效果。connect方法的代码如下:
import React from "react"; import {Consumer} from "./context"; export default mapState => { return WrappedComponent => { const Component = props => (<Consumer> { value => { let mappedProps = mapState(value); return <WrappedComponent {...props} {...mappedProps}/> } } </Consumer>); Component.displayName = `connect(${WrappedComponent.displayName || WrappedComponent.name || "Component"})`; return Component; } }; 复制代码
简单解释一下:connect方法需要传入一个mapState方法,mapState方法是context的value映射方法,当调用connect方法后,会依旧返回一个函数;该函数实际是一个高阶函数工厂,将传入的WrappedComponent组件用Consumer包裹里面,并结合之前的mapState映射得到具体的计算后的props属性,并把这些props属性都赋予给WrappedComponent。这样,我们在之后想要得到context时,只需要简单调用一下该方法即可。
再结合一个例子看看怎么使用connect方法:假如现在有一个App用户显示学生的相关信息;学生的信息包含了name,age,gender三个属性;此外有两个组件Student、StudentGender;Student用于显示学生的name,age,并且有一个 +
按钮,点击就会在当前年龄加一岁。
层级关系如下:App -> StudentContainer -> Student
App组件的代码如下:
import React, {Component} from 'react'; import {Provider} from "./context"; import StudentContainer from "./StudentContainer"; class App extends Component { onIncreaseAge = () => { this.setState(preState => ({ age: preState.age + 1 })) }; state = { name: "张三", age: 12, gender: "男", onIncreaseAge: this.onIncreaseAge }; render() { return ( <div className="App"> <Provider value={this.state}> <StudentContainer/> </Provider> </div> ); } } export default App; 复制代码
在App组件中,我们将student的属性以及增加年龄的方法一同传递给了context,使得子组件既能获得属性,也能调用修改属性的方法。
Student组件的代码如下:
import React from "react"; import {connect} from "./context"; const Student = ({studentName, studentAge, onIncreaseAge}) => { return <div> <span className="title">Student:</span> <ul> <li>name: {studentName}</li> <li>age: {studentAge} <button onClick={onIncreaseAge}>+</button> </li> </ul> </div>; }; const mapState = state => ({ studentName: state.name, studentAge: state.age, onIncreaseAge: state.onIncreaseAge }); export default connect(mapState)(Student); 复制代码
可以看到,当我们使用了connect方法后,Student组件就变成了一个傻瓜组件,只需要专心负责显示数据即可。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 前端状态管理与有限状态机
- 为管理复杂组件状态困扰?试试 vue 简单状态管理 Store 模式
- 基于有限状态机的广告状态管理方案及实现
- 基于有限状态机的广告状态管理方案及实现
- vue状态管理演进
- 浅析前端状态管理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
《Hello Ruby:儿童编程大冒险》(平装)
(芬兰)琳达·刘卡斯 / 窝牛妈 / 浙江人民美术出版社 / 2018
快来认识Ruby——一个想象力丰富,喜欢解决难题的女生。Ruby认识了一群新朋友:聪明的雪豹、友好的狐狸、忙碌的机器人等等。这本书以讲故事的方式向孩子们介绍了基础的计算思维,比如拆分问题,制定分步计划,寻找规律,打破思维定势等等;之后,通过一系列鼓励探索和创造的练习和活动,孩子们对这些关乎编程核心问题的基本概念有了进一步的理解。一起来看看 《《Hello Ruby:儿童编程大冒险》(平装)》 这本书的介绍吧!