前言
React作为前端最🔥的框架之一,但是有的时候我们仅限于能用的阶段,有一些高级用法,我们在日常开发中却很少涉足。但是一旦用起来,我们就能发现它的方便和强大之处,我们就会越来越发现我们已经离不开它了!这就像是刚用React时,我内心是拒绝的,但是现在我已经离不开它了,越来越不能理解以前自己为什么抱着JQuery不放呢!
今天我们重点讲一下Context这个高级API,以及如何封装它,让它更加易用!
Context简介
Context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props 属性。
在一个典型的 React 应用中,数据是通过props属性由上向下(由父及子)的进行传递的,但这对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI主题),这是应用程序中许多组件都所需要的。 Context 提供了一种在组件之间共享此类值的方式,而不必通过组件树的每个层级显式地传递 props 。
简单说就是,当你不想在组件树中通过逐层传递props或者state的方式来传递数据时,可以使用Context来实现跨层级的组件数据传递。
假设我们有一种场景,我们有一个业务容器App,里面有一个组件容器Container,Container组件内包含一个Form表单,Form表单里面有一个提交按钮SubmitButton。假如使用props传递,我们就不得不传递四层。
看到了吗?很方便吧!这里我们使用Context,在组件可以直接通过context获取最顶层绑定的值,避免了一层层传递props的麻烦,也减少出错的可能性。
如何使用Context
如果要Context发挥作用,需要用到两种组件,一个是Context生产者(Provider),通常是一个父节点,另外是一个Context的消费者(Consumer),通常是一个或者多个子节点。所以Context的使用基于生产者消费者模式。
Context 设计目的是为共享那些被认为对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。例如,在下面的代码中,我们通过一个“theme”属性手动调整一个按钮组件的样式,使用context,我们可以避免通过中间元素传递props。
// 创建一个 theme Context, 默认 theme 的值为 light
const ThemeContext = React.createContext('light');
function ThemedButton(props) {
// ThemedButton 组件从 context 接收 theme
return (
<ThemeContext.Consumer>
{theme => <Button {...props} theme={theme} />}
</ThemeContext.Consumer>
);
}
// 中间组件
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
class App extends React.Component {
render() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
一种更简单的使用方式
看了上面的使用方式,有没有觉得还是有一些不爽,有没有简单一点的方式呢,或者能不能帮我封装一下呢? 当然可以,Javascript工程师是无所不能的!!!
首先是我们的provider.js,这个就是我们封装的context使用工具
import React, { Component } from 'react';
export const Context = React.createContext();
export class ContextProvider extends Component {
render() {
return (
<Context.Provider value={this.props.context}>
{this.props.children}
</Context.Provider>
);
}
}
/**
* 用注解的方式给子组件注入属性
*/
export const injectContext = (contexts) => RealComponent => {
return class extends Component {
render() {
return (
<Context.Consumer>
{context => {
// 将顶层的context分发到各层
let mapContext = {};
if(Array.isArray(contexts)) {
contexts.map(item => {
mapContext[item] = context[item];
});
}
return (
<RealComponent {...mapContext} {...this.props} />
)
}}
</Context.Consumer>
);
}
};
};
还是举个栗子,来让大家明白上述封装的方法的方便之处。 假如要实现GrandParent -> Parent -> Son,从GrandParent组件传递属性到GrandSon组件,每个组件都有一个独立的文件。
先看入口文件,我们在入口文件进行绑定上下文,使用provider里面的ContextProvider类,这里我们主要绑定了propA和propB。
// 入口文件
import React, { PureComponent } from 'react';
import { ContextProvider } from './provider';
import GrandParent from './GrandParent';
class Index extends PureComponent {
render () {
return (
<ContextProvider context={{
propA: 'propA',
propB: 'propB'
}}>
<GrandParent />
</ContextProvider>
)
}
}
Parent组件没什么特殊的
import React, { PureComponent } from 'react';
import Son from './Son';
class Index extends PureComponent {
render () {
return (
<Son />
)
}
}
Son组件是真正使用属性propA和propB的地方,我们通过ES6的Decorator实现,非常方便。注入后,可以像props一样使用。
import React, { PureComponent } from 'react';
import { injectContext } from './provider';
import Son from './Son';
@injextContext(['propA', 'propB'])
class Index extends PureComponent {
render () {
return (
<div>
<span>propA为{this.props.propA}</span>
<span>propB为{this.props.propB}</span>
</div>
)
}
}
后记
在这一小节中,我们主要讲了React的一个高级语法Context,而且为了使用方便,我们封装了ContextProvider类和injextContext方法,使用时利用ES6的Decorator语法糖,非常简便。大家在日常开发中,也可以封装出一些这样的小工具,可以极大提升开发效率。
最后元旦快到了,祝大家新年快乐!!!
@Author: WaterMan