Context(精读React官方文档—15)

简介: Context(精读React官方文档—15)

Context有什么作用?

官方描述:Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。如果属性只能通过props从上往下进行传递,在有些应用场景下是非常繁琐的,比如UI主题的继承,Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。

解读

  1. 通过Context可以让组件树之间进行数据传递,并且不需要手动添加props.

何时使用Context?

Context设计的目的是为了共享那些对于一个组件树而言是全局的数据。

例如下面的例子,通过theme属性手动调整一个按钮组件的样式

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} />;
  }
}
复制代码
  • 通过props可以避免中间元素传递props
// 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() {
  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} />;
  }
}
复制代码

使用Context之前的考虑

  • 带来的问题

Context的主要应用场景是不同层级的组件需要访问一些相同的数据,这可能会使得组件的复用性变差。

  • 层层传递props带来的麻烦
<Page user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<Link href={user.permalink}>
  <Avatar user={user} size={avatarSize} />
</Link>
复制代码

解读

如果在最后只有 Avatar 组件真的需要 user 和 avatarSize,那么层层传递这两个 props 就显得非常冗余。而且一旦 Avatar 组件需要更多从来自顶层组件的 props,你还得在中间层级一个一个加上去,这将会变得非常麻烦。

  • 一种无需Context的解决方案(将Avatar组件自身传递下去)
function Page(props) {
  const user = props.user;
  const userLink = (
    <Link href={user.permalink}>
      <Avatar user={user} size={props.avatarSize} />
    </Link>
  );
  return <PageLayout userLink={userLink} />;
}
// 现在,我们有这样的组件:
<Page user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<PageLayout userLink={...} />
// ... 渲染出 ...
<NavigationBar userLink={...} />
// ... 渲染出 ...
{props.userLink}
复制代码
  • 上面做法带来的问题
  1. 使得高层组件更加复杂。
  • 传递多个组件的情况
function Page(props) {
  const user = props.user;
  const content = <Feed user={user} />;
  const topBar = (
    <NavigationBar>
      <Link href={user.permalink}>
        <Avatar user={user} size={props.avatarSize} />
      </Link>
    </NavigationBar>
  );
  return (
    <PageLayout
      topBar={topBar}
      content={content}
    />
  );
}
复制代码

API

React.createContext

const MyContext = React.createContext(defaultValue);
复制代码

解读

创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。

注意事项

只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。此默认值有助于在不使用 Provider 包装组件的情况下对组件进行测试。注意:将 undefined 传递给 Provider 的 value 时,消费组件的 defaultValue 不会生效。

Context.Provider

<MyContext.Provider value={/* 某个值 */}>
复制代码

解读

  1. 每个Context对象都会返回一个Provider组件,这个组件允许订阅context对象的变化。
  2. Provider接收一个value属性,传递给消费组件。
  3. Provider中的value值发生变化的时候,其所有的消费组件都会重新渲染。

Class.contextType

class MyClass extends React.Component {
  componentDidMount() {
    let value = this.context;
    /* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /* 基于 MyContext 组件的值进行渲染 */
  }
}
MyClass.contextType = MyContext;
复制代码

挂载在类上的contextType属性会被重新赋值为一个有React.createContext()创建的Context对象。这个属性能够让我们使用this.context来使用最近Context上的那个值。

class MyClass extends React.Component {
  static contextType = MyContext;
  render() {
    let value = this.context;
    /* 基于这个值进行渲染工作 */
  }
}
复制代码

Context.Consumer

  • 这个API可以让函数式组件使用Context,相当于类组件的this.context
<MyContext.Consumer>
  {value => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>
复制代码

解读

Consumer内部的函数接收当前的context值,然后返回React节点,传递给函数的value值等价于组件树上方离这个context最近的Provider提供的value值。

Context.displayName

这个属性是用来辅助Reacat DevTools来使用字符串确定context要显示的内容的。

const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';
<MyContext.Provider> // "MyDisplayName.Provider" 在 DevTools 中
<MyContext.Consumer> // "MyDisplayName.Consumer" 在 DevTools 中
复制代码

示例

动态Context

对上面的theme例子使用动态值。

theme-context.js

export const themes = {
  light: {
    foreground: '#000000',
    background: '#eeeeee',
  },
  dark: {
    foreground: '#ffffff',
    background: '#222222',
  },
};
export const ThemeContext = React.createContext(
  themes.dark // 默认值
);
复制代码

themed-button.js

import {ThemeContext} from './theme-context';
class ThemedButton extends React.Component {
  render() {
    let props = this.props;
    let theme = this.context;
    return (
      <button
        {...props}
        style={{backgroundColor: theme.background}}
      />
    );
  }
}
ThemedButton.contextType = ThemeContext;
export default ThemedButton;
复制代码

app.js

import {ThemeContext, themes} from './theme-context';
import ThemedButton from './themed-button';
// 一个使用 ThemedButton 的中间组件
function Toolbar(props) {
  return (
    <ThemedButton onClick={props.changeTheme}>
      Change Theme
    </ThemedButton>
  );
}
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      theme: themes.light,
    };
    this.toggleTheme = () => {
      this.setState(state => ({
        theme:
          state.theme === themes.dark
            ? themes.light
            : themes.dark,
      }));
    };
  }
  render() {
    // 在 ThemeProvider 内部的 ThemedButton 按钮组件使用 state 中的 theme 值,
    // 而外部的组件使用默认的 theme 值
    return (
      <Page>
        <ThemeContext.Provider value={this.state.theme}>
          <Toolbar changeTheme={this.toggleTheme} />
        </ThemeContext.Provider>
        <Section>
          <ThemedButton />
        </Section>
      </Page>
    );
  }
}
ReactDOM.render(<App />, document.root);
复制代码

在嵌套组件中更新Context

在嵌套很深的组件中更新context是很必要的,可以通过context传递函数,是的consumers组件更新context

theme-context.js

// 确保传递给 createContext 的默认值数据结构是调用的组件(consumers)所能匹配的!
export const ThemeContext = React.createContext({
  theme: themes.dark,
  toggleTheme: () => {},
});
复制代码

theme-toggle-button.js

import {ThemeContext} from './theme-context';
function ThemeTogglerButton() {
  // Theme Toggler 按钮不仅仅只获取 theme 值,
  // 它也从 context 中获取到一个 toggleTheme 函数
  return (
    <ThemeContext.Consumer>
      {({theme, toggleTheme}) => (
        <button
          onClick={toggleTheme}
          style={{backgroundColor: theme.background}}>
          Toggle Theme
        </button>
      )}
    </ThemeContext.Consumer>
  );
}
export default ThemeTogglerButton;
复制代码

app.js

import {ThemeContext, themes} from './theme-context';
import ThemeTogglerButton from './theme-toggler-button';
class App extends React.Component {
  constructor(props) {
    super(props);
    this.toggleTheme = () => {
      this.setState(state => ({
        theme:
          state.theme === themes.dark
            ? themes.light
            : themes.dark,
      }));
    };
    // State 也包含了更新函数,因此它会被传递进 context provider。
    this.state = {
      theme: themes.light,
      toggleTheme: this.toggleTheme,
    };
  }
  render() {
    // 整个 state 都被传递进 provider
    return (
      <ThemeContext.Provider value={this.state}>
        <Content />
      </ThemeContext.Provider>
    );
  }
}
function Content() {
  return (
    <div>
      <ThemeTogglerButton />
    </div>
  );
}
ReactDOM.render(<App />, document.root);
复制代码

消费多个Context

为了确保context快速进行重渲染,React需要使每一个消费组件的context在组件树中成为一个单独的节点。

// Theme context,默认的 theme 是 “light” 值
const ThemeContext = React.createContext('light');
// 用户登录 context
const UserContext = React.createContext({
  name: 'Guest',
});
class App extends React.Component {
  render() {
    const {signedInUser, theme} = this.props;
    // 提供初始 context 值的 App 组件
    return (
      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Layout />
        </UserContext.Provider>
      </ThemeContext.Provider>
    );
  }
}
function Layout() {
  return (
    <div>
      <Sidebar />
      <Content />
    </div>
  );
}
// 一个组件可能会消费多个 context
function Content() {
  return (
    <ThemeContext.Consumer>
      {theme => (
        <UserContext.Consumer>
          {user => (
            <ProfilePage user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>
  );
}
复制代码

注意事项

  • 下面的代码,当每一次Provider重渲染时,会重渲染下面的所有消费者组件,因为value属性总是被赋值为新的对象。
class App extends React.Component {
  render() {
    return (
      <MyContext.Provider value={{something: 'something'}}>
        <Toolbar />
      </MyContext.Provider>
    );
  }
}
复制代码

解决办法

将value状态提升到父节点的state里。

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: {something: 'something'},
    };
  }
  render() {
    return (
      <MyContext.Provider value={this.state.value}>
        <Toolbar />
      </MyContext.Provider>
    );
  }
}
复制代码

欢迎大家关注我的专栏,一起来学习React!

相关文章
|
7月前
|
前端开发 JavaScript API
【第54期】一文读懂React文档
【第54期】一文读懂React文档
85 1
|
7月前
|
前端开发 API
React 之 Context 的变迁与背后实现
React 之 Context 的变迁与背后实现
69 0
|
7月前
|
前端开发 JavaScript 定位技术
Docusaurus框架——react+antd+echarts自定义mdx生成图表代码解释文档
Docusaurus框架——react+antd+echarts自定义mdx生成图表代码解释文档
690 0
|
7月前
|
前端开发 JavaScript API
React 生态系统:路由、状态管理、调试、测试、组件库、文档……
React 生态系统:路由、状态管理、调试、测试、组件库、文档……
119 0
|
7月前
|
前端开发 安全 JavaScript
React 正式推出全新官方文档!
React 正式推出全新官方文档!
123 0
|
7月前
|
前端开发
React中兄弟组件通信和组件跨级传递Context的使用
React中兄弟组件通信和组件跨级传递Context的使用
|
7月前
|
前端开发
react的context状态树怎么使用
react的context状态树怎么使用
63 0
|
前端开发
react导出word文档?
react导出word文档?
|
前端开发
React的context传值方法介绍
1.在src在创建一个context.js文件
React的context传值方法介绍
|
JavaScript 前端开发 容器
React-跨组件通讯-context
React-跨组件通讯-context
62 0