一、为什么要用context
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
当我们子组件很多,并且都在同一个组件树上时,我们需要把props传递非常多次,所有我们可以用context把一些每个组件的公共数据通过Provider传递。
Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据。
二、如何使用Context?
(1) Reract官方给出的API :
React.createContext:创建一个上下文的容器(组件), defaultValue可以设置共享的默认数据
const defaultContext = React.createContext(defaultValue); // 没解构Provider和Consumer,后面可以通过 . 的方式设置。适用于多个context时候
const {Provider, Consumer} = React.createContext(defaultValue); // 直接解构出Provider和Consumer,适用于只有一个context的时候
(2) Provider(生产者): 用于生产共享数据的地方。生产什么呢? 那就看value定义的是什么了。value:放置共享的数据。
<Provider value={/*共享的数据*/}>
/*里面可以渲染对应的内容*/
</Provider>
(3) Consumer(消费者):这个可以理解为消费者。 他是专门消费供应商(Provider 上面提到的)产生数据。Consumer需要嵌套在生产者下面。才能通过回调的方式拿到共享的数据源。当然也可以单独使用,那就只能消费到上文提到的defaultValue
<Consumer>
{value => ()/*根据上下文 进行渲染相应内容*/}
</Consumer>
1.使用Context
const ThemeContext = React.createContext('red')
class App extends React.Component {
render() {
return (
// 01-Provider 将theme主题 通过value属性传递给 所有子元素 子组件的props.children
// Provider是一个组件,允许消费组件 订阅context变化
// Provider value属性值发生变化,内部所有的消费组件都会重新渲染
// 若组件树中,没有匹配到provider,则 使用默认值 defaultValue
<ThemeContext.Provider value="green">
<Toolbar_1 />
</ThemeContext.Provider>
)
}
}
class Toolbar_1 extends extends React.Component {
render(){
return (
// 03-通过Consumer 订阅context的变更
// 使用 函数作为子元素,函数参数 就是当前的context的值(provider value属性值)
// 函数返回值就是react的一个结点元素
<ThemeContext.Consumer>
{
(value) => {
return (
<div>
<div style={{ color: value }}>Toolbar</div>
<ThemeButton theme={value} />
</div>
)
}
}
</ThemeContext.Consumer>
)
}
}
// 02-使用contextType
// 只能在类组件内使用,函数组件内使用 不好使
// 但是<Consumer>在函数组件和类组件内都可以使用
class Toolbar_2 extends React.Component {
// 也可以使用默认属性赋值
static contextType = ThemeContext
render() {
return (
<div>
<div style={{ color: this.context }}>Toolbar</div>
<ThemeButton theme={this.context} />
</div>
)
}
}
// Toolbar.contextType = ThemeContext // this.context = context
class ThemeButton extends React.Component {
render() {
return (
<ThemeContext.Consumer>
{
(value) => (
<button style={{ color: value }}>主题按钮</button>
)
}
</ThemeContext.Consumer>
)
}
}
2.动态使用Context
当我们传递个value的值为state时,我们state更新,value也会跟着更新,从而实现动态使用context的效果
// 动态使用context
// 01-定义主题
const themes = {
light: {
color: "red",
background: "#fff"
},
dark: {
color: "green",
background: "#000"
}
}
// 02-定义context
const ThemeContext = React.createContext(themes.dark)
// 03-提供provider
class App extends React.Component {
constructor(props) {
super(props)
// theme 被定义成状态维护的主题 动态的值
this.state = {
theme:themes.light
}
}
toggleTheme = ()=>{
// 注意 state 对原有状态 有依赖
this.setState((preState)=>{
return {
theme:preState.theme === themes.light ? themes.dark : themes.light
}
})
}
// provider value 和 thi.state 有关联后
// 通过setState改变value值回触发重新渲染,所有的子组件都会重新渲染(重要),shouldComponentUpdate有效
render() {
return (
<ThemeContext.Provider value={this.state.theme}>
<Toolbar changeTheme={this.toggleTheme}/>
</ThemeContext.Provider>
)
}
}
class Toolbar extends React.Component {
static contextType = ThemeContext
render() {
let {changeTheme} = this.props
let { color, background } = this.context
return (
<div>
<div style={{ color: color, background: background }}>ToolBar</div>
<ThemeButton {...this.props}/>
</div>
)
}
}
class ThemeButton extends React.Component {
static contextType = ThemeContext
render() {
let {changeTheme} = this.props
let { color, background } = this.context
return (
<button style={{color, background}} onClick={changeTheme}>主题按钮</button>
)
}
}
3.当多个context同时使用时
当有多个context,contextType不好使了,要用Consumer嵌套。
当然后面还有更好的办法useContext,不过只能适用于函数组件。
class App extends React.Component {
state = {
age:60
}
handleChange = ()=>{
this.setState({
age:this.state.age = 80
})
}
render(){
return (
<div>
<h2>app:{this.state.age}</h2>
<Father/>
<button onClick={this.handleChange}>Change Age</button>
</div>
)
}
}
const MoneyContext = React.createContext(50)
const HouseContext = React.createContext("house")
class Father extends React.Component {
state = {
money: 100,
house: "apartment"
}
render() {
// 出现多个context传递数据,可以使用Provider进行嵌套
return (
<MoneyContext.Provider value={this.state.money}>
<HouseContext.Provider value={this.state.house} >
<Son />
</HouseContext.Provider>
</MoneyContext.Provider>
)
}
}
function Son() {
return (
<MoneyContext.Consumer>
{
(money) => {
return (
<div>
<p>Son组件,获取father值为:{money}</p>
<br />
<GrandSon />
</div>
)
}
}
</MoneyContext.Consumer>
)
}
function GrandSon() {
// Consumer 包裹内容时,只能是一个函数,不能直接是一个组件
// 多个Context 时候,可以使用嵌套函数
return (
<MoneyContext.Consumer>
{
(money) => (
<HouseContext.Consumer>
{
(house) => (
<div>
<p>GrandSon组件,获取father的钱为:{money}</p>
<p>GrandSon组件,获取father的房子为:{house}</p>
</div>
)
}
</HouseContext.Consumer>
)
}
</MoneyContext.Consumer>
)
}
// class GrandSon extends React.Component {
// // contextType使用条件:
// // 01-类组件
// // 02-只有一个context时,如果有多个context时会后面的覆盖前面的,所以多个时直接使用consumer嵌套
// static contextType = MoneyContext
// static contextType = HouseContext
// render() {
// console.log(contextType);
// let {house,money} = this.context // 不行,不能拿两个,只能拿到后面的
// return (
// <div>
// <p>GrandSon组件,获取father的钱为:{money}</p>
// <p>GrandSon组件,获取father的房子为:{house}</p>
// </div>
// )
// }
// }