react-跨组件级通信- Context
之前编写组件都是通过props或者state的方式来传递组件, 但组件层级多起来时候,就会出现多次从顶层组件一直传递到最底层的组件中使用。
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
Context常用API
React.createContext
创建一个 Context 对象。
const DemoContext = React.createContext(defaultValue);
// defaultValue 默认值
Context.Provider
Provider 接收一个 value属性,传递给消费组件。
<DemoContext.Provider value={/* 某个值 */}>
当 Provider 的 value
值发生变化时,它内部的所有消费组件都会重新渲染。
Context.Consumer
<DemoContext.Consumer>
{value => /* 基于 context 值进行渲染*/}
</DemoContext.Consumer>
这种方法需要一个函数作为子元素(function as a child)。这个函数接收当前的 context 值,并返回一个 React 节点。传递给函数的 value
值等等价于组件树上方离这个 context 最近的 Provider 提供的 value
值。如果没有对应的 Provider,value
参数等同于传递给 createContext()
的 defaultValue
。
基本使用
基本使用context
import React, { useState } from 'react'
// 创建一个context对象, 并设置默认值为 测试
const DemoContext = React.createContext('测试')
function ContextPage() {
return (
<DemoContext.Consumer>
{(ctx) => {
console.log('ctx', ctx) // 测试
return <div>{ctx}</div>
}}
</DemoContext.Consumer>
)
}
export default ContextPage
注意点
只有组件Consumer没有匹配到Provider时, CreateContext设置的默认值("测试")才会生效. 这有助于在不使用 Provider 包装组件的情况下对组件进行测试。
将 undefined
传递给 Provider 的 value 时,消费组件的 defaultValue
不会生效
import React, { useState } from 'react'
// 创建一个context对象, 并设置默认值为 测试
const DemoContext = React.createContext('测试')
function ContextPage() {
return (
<DemoContext.Provider>
<DemoContext.Consumer>
{(ctx) => {
console.log('ctx', ctx) // 此时不生效 undefined
return <div>{ctx}</div>
}}
</DemoContext.Consumer>
</DemoContext.Provider>
)
}
export default ContextPage
切换主题颜色案例
1.创建context对象
// context.js
import React from 'react'
export const ThemeContext = React.createContext({ themeColor: 'pink' })
export const ThemeProvider = ThemeContext.Provider
export const ThemeConsumer = ThemeContext.Consumer
2.创建提供商 provider
// contextPage.js
import React, { useState } from 'react'
import { ThemeProvider } from './context'
import ConsumerPage from './ConsumerPage'
import UseContextPage from './UseContextPage'
function ContextPage() {
const [theme, setTheme] = useState({ themeColor: 'red' })
return (
<ThemeProvider value={theme}>
<button
onClick={() => {
theme.themeColor == 'red'
? setTheme({ themeColor: 'green' })
: setTheme({ themeColor: 'red' })
}}
>
切换主题
</button>
{/* 基本使用 */}
<ConsumerPage />
{/* hooks写法 */}
<UseContextPage />
</ThemeProvider>
)
}
export default ContextPage
3.创建消费者 consume
import React, { Component } from 'react'
import { ThemeConsumer, ThemeContext } from './context'
export default class ConsumerPage extends Component {
// 可以使用 static 这个类属性来初始化你的 contextType。
// static contextType = ThemeContext
render() {
let value = this.context
console.log('value', value)
return (
<div>
<h3>ConsumerPage</h3>
<ThemeConsumer>
{(themeContext) => {
console.log('themeContext', themeContext)
return <div className={themeContext.themeColor}>我是内容</div>
}}
</ThemeConsumer>
</div>
)
}
}
//挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象。
ConsumerPage.contextType = ThemeContext
注意点:
- 可以通过挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象。
- 可以使用 static 这个类属性来初始化你的 contextType,拿到Context对象
- 可以使用ThemeConsumer的render-prop 拿到Contxt对象
4.Hook中useContext写法
const {themeColor} = useContext(ThemeContext);
接收一个 context 对象(React.createContext
的返回值)并返回该 context 的当前值。useContext 的参数必须是 context 对象本身
import React, { useContext } from 'react'
import { ThemeContext } from './context'
export default function UseContextPage(props) {
// 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。
// 当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。
/**
别忘记 useContext 的参数必须是 context 对象本身:
正确: useContext(MyContext)
错误: useContext(MyContext.Consumer)
错误: useContext(MyContext.Provider)
*/
const { themeColor } = useContext(ThemeContext)
return (
<div className="border">
<h3 className={themeColor}>UseContextPage</h3>
</div>
)
}
当组件上层最近的 <MyContext.Provider>
更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext
provider 的 context value
值。
即使祖先使用 React.memo
或 shouldComponentUpdate
,也会在组件本身使用 useContext
时重新渲染。
- 效果
useContext的几个优化方案
1.将组件一分为二,放在memo
两者之间
- 可以通过将组件拆分为两个并将更具体的道具传递给内部的道具来优化渲染。
import React, { memo, useContext, useMemo } from 'react'
import { ThemeContext } from './context'
export default function UseContextPage(props) {
const { themeColor } = useContext(ThemeContext)
return (
// 处理其他的渲染逻辑
<Two themeColor={themeColor} />
)
// return useMemo(() => <Three className={themeColor} />, [themeColor])
}
const Two = memo(({ themeColor }) => {
return (
<div className={themeColor}>
3232
<h3>UseContextPage</h3>
</div>
)
})
2.一个组件useMemo
内部
- 可以通过将返回值包装在其中
useMemo
并指定其依赖项来将其保留在单个组件中。我们的组件仍然可以重新执行,但是如果所有useMemo
输入都相同,React不会重新渲染子树。
import React, { memo, useContext, useMemo } from 'react'
import { ThemeContext } from './context'
export default function UseContextPage(props) {
const { themeColor } = useContext(ThemeContext)
return useMemo(() => <Three className={themeColor} />, [themeColor])
}
function Three(props) {
return (
<div className={props.className}>
3232
<h3>UseContextPage</h3>
</div>
)
}