这是牛津词典里有关 context 的解释。通常我们在编程当中会把 context 叫做 上下文,不过我认为这里似乎叫做 语境更合适一些。但是不管是叫做 上下文或是 语境,都还是有些抽象。
在学生时代,语文试卷中都会有个阅读理解部分,通常会有个问题类似于“结合上下文,x x x x x x x”,例如:
…
林冲大叫一声“啊也!”
…
问:这句话林冲的“啊也”表达了林冲怎样的心里?
答:啊你妈个头啊!
看,一篇文章,给你摘录一段,没前没后,你读不懂。因为有 语境,就是语言环境存在,一段话说了什么,要通过上下文(文章的上下文)来推断。
那么从编程的角度又该如何理解呢?通常我们写代码的时候,除了一些简单的函数不需要外部变量,其他的复杂些的函数大多会依赖一些外部变量。而一旦需要外部变量,那么这段代码就不是完整的,不是能够独立运行的,而这里这些程序运行所必需的外部变量(也可以理解为前置条件),就叫做 上下文。
首先,react 在 16.3 引入了context,经过不过的迭代,到现在16.8.6的版本中,
官方对context的定义为 — Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。归类于高级指引部分,属于react的高级api。
通常在我们写react组件的时候,数据传递一般采用单相数据流的方式,这样可以使数据流向变得的简单而清晰。但在某些使用场景中需要共享一些对于一个组件树而言是**“全局”**的数据,这时就需要在每一个层级手动修改传输的props,过于麻烦。
例如官方文档中提供的demo:
class App extends React.Component {
render() {
return <Toolbar theme="dark" />;
}
}
function Toolbar(props) {
// Toolbar 组件接受一个额外的“theme”属性,然后传递给 ThemedButton 组件。
// 如果应用中每一个单独的按钮都需要知道 theme 的值,这会是件很麻烦的事,
// 因为必须将这个值层层传递所有组件。
return (
<div>
<ThemedButton theme={
props.theme} />
</div>
);
}
class ThemedButton extends React.Component {
render() {
return <Button theme={
this.props.theme} />;
}
}
在上述代码中,修改Button的theme,那么需要从App中修改theme,传给Toolbar,并由Toolbar传给ThemedButton使用,如此层层传递。如果ThemedButton与App之前还存在更多的嵌套关系,那么必需将theme在中间的各层级间层层传递,显然,这样过于繁琐了。
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “dark” 作为当前的值传递下去。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// React 会往上找到最近的 theme Provider,然后使用它的值。
// 在这个例子中,当前的 theme 值为 “dark”。
static contextType = ThemeContext;
render() {
return <Button theme={
this.context} />;
}
}
这是16.8.6版本中的文档demo(本文不对16.x之前的context使用方式做探讨,有兴趣同学可自行研究)
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “dark” 作为当前的值传递下去。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// React 会往上找到最近的 theme Provider,然后使用它的值。
// 在这个例子中,当前的 theme 值为 “dark”。
static contextType = ThemeContext;
render() {
// 挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象。这能让你使用 this.context 来消费最近 Context 上的那个值。
return <Button theme={
this.context} />;
}
}
4.中间无需使用theme的组件,不需要层层传递props
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// React 会往上找到最近的 theme Provider,然后使用它的值。
// 在这个例子中,当前的 theme 值为 “dark”。
static contextType = ThemeContext;
render() {
return (
<ThemeContext.Consumer>
//Consumer容器,可以拿到上文传递下来的 theme 属性,并可以展示对应的值
{
(theme) =>
<Button theme={
theme} />
}
</ThemeContext.Consumer>
);
}
}
1.因为每次Context值变更时,Consumer都会接受到相应的变化通知,这里可能会有一些陷阱,当 Provider 的父组件进行重渲染时,可能会在 Consumers 组件中触发意外的渲染。例如
class App extends React.Component {
render() {
//当每一次 Provider 重渲染时(即每次render时{something: 'something'}都指向一个新对象,拓展1),以下的代码会重渲染所有下面的 consumers 组件,因为 value 属性总是被赋值为新的对象
return (
<Provider value={
{
something: 'something'}}>
<Toolbar />
</Provider>
);
}
}
为了防止这种情况,将 value 状态提升到父节点的 state 里:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: {
something: 'something'},
};
}
render() {
return (
<Provider value={
this.state.value}>
<Toolbar />
</Provider>
);
}
}
2.组件的复用性降低
1.引用类型
2.mobx-react / react-router / react-redux 均适用context,有兴趣可自行探究。