前言
最近在项目发现了一些看起来很神奇的代码,一个组件 A 的方法通过 props
传到组件 B,然后到组件 C,再到组件 D,再到组件 E,最后到组件 D, 简直是千层饼😂
提问一番后知道了这种将属性深层传递的现象叫 Prop Drilling
,本文说明如何使用 React 的 Context API 避免这种现象。
什么是 Context
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但此种用法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。
Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。这种组件共享的以及变化不频繁的状态正是 Context 用武之地。
如何使用 Context
举个例子🌰 说明
创建 Context
首先用 React.createContext
方法创建一个 AuthContext
,表示当前应用的认证状态,创建时可以传入默认值。
// AuthContext.js
export const AuthContext = React.createContext({
user: null,
isAuthenticated: false,
})
然后创建 AuthContextProvider
组件,在组件中初始化状态,定义改变状态的方法,将这些数据和方法传递给 Context.Provider
的 value
属性 ,value
可以接收数据和方法,最后在组件中渲染子组件 children
。
// AuthContext.js
export class AuthContextProvider extends Component {
state = {
user: null,
isAuthenticated: false,
}
login = (user) => {
// ...
}
logout = () => {
// ...
}
render() {
return (
<AuthContext.Provider value={(this.state, this.login, this.logout)}>
{this.props.children}
</AuthContext.Provider>
)
}
}
使用 Context
有多种方式获得 Context 中的数据
- 使用
Context.Comsumer
// AppHeader.js
<AuthContext.Consumer>
{(authContext) => (
<header>
// ...
</header>
)}
</AuthContext.Consumer>
- 使用组件的
ContextType
属性,将contextType
属性赋值为想要传入的 Context
// AppFooterButtons.js
class AppFooterButtons extends Component {
static contextType = AuthContext
render() {
return (
<div>
<button onClick={this.login}>Login</button>
<button onClick={this.context.logout}>Logout</button>
</div>
)
}
}
- 使用
useContext
函数式组件可以使用 useContext
方法获取状态数据,可以同时获取多个状态。
// AppBody.js
const AppBoy = () => {
const authContext = useContext(AuthContext)
const { languages, current } = useContext(LanguageContext)
return (
<div>
<h1>
Hello, {authContext.isAuthenticated ? authContext.user.name : 'World'}
</h1>
<p>Currrent Lang: {languages[current].lang}</p>
</div>
)
}
修改 Context
- 对于 class 组件,使用
setState
方法更新组件的state
。
// AuthContext.js
export class AuthContextProvider extends Component {
// ...
login = (user) => {
if (this.state.isAuthenticated) {
return
}
this.setState({
user,
isAuthenticated: true,
})
}
logout = () => {
this.setState({
user: null,
isAuthenticated: false,
})
}
// ...
}
- 函数式组件使用
useState
// LanguageContext.js
export const LanguageContextProvider = (props) => {
// ...
const [current, setCurrent] = useState(0)
const changeLanguage = (val) => {
setCurrent(val)
}
// ...
}
最终使用 Context 将应用中通用状态统一管理,无需再层层传递。
结论
本文说明什么是 Context,以及使用 Context 的多种方式。使用 Context 可以有效的避免 Prop Drilling
现象,将需要深层传递的属性和方法提取出来,达到共用的目的。应用 Context
的最佳时机是应用的一些全局性,不会频繁变化的数据。
注意事项
Context 主要应用场景在于_很多_不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复 用性变差。
如果你只是想避免层层传递一些属性,组件组合(component composition)有时候是一个比 context 更好的解决方案。
参考
Avoid Prop Drilling with React Context
Prop Drilling
Context