Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但这种做法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。

使用Context 前的考虑

Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。
但是,有的时候在组件树中很多不同层级的组件需要访问同样的一批数据。Context 能让你将这些数据向组件树下所有的组件进行“广播”,所有的组件都能访问到这些数据,也能访问到后续的数据更新。使用 context 的通用的场景包括管理当前的 locale,theme,或者一些缓存数据,这比替代方案要简单的多。

motivation

problem

属性需要层层传递。

  1. class App extends React.Component {
  2. render() {
  3. return <Toolbar theme="dark" />;
  4. }
  5. }
  6. function Toolbar(props) {
  7. // highlight-range{1-4,7}
  8. // The Toolbar component must take an extra "theme" prop
  9. // and pass it to the ThemedButton. This can become painful
  10. // if every single button in the app needs to know the theme
  11. // because it would have to be passed through all components.
  12. return (
  13. <div>
  14. <ThemedButton theme={props.theme} />
  15. </div>
  16. );
  17. }
  18. class ThemedButton extends React.Component {
  19. render() {
  20. return <Button theme={this.props.theme} />;
  21. }
  22. }

solution

通过 static contextType 解决。

  1. // highlight-range{1-4}
  2. // Context lets us pass a value deep into the component tree
  3. // without explicitly threading it through every component.
  4. // Create a context for the current theme (with "light" as the default).
  5. const ThemeContext = React.createContext('light');
  6. class App extends React.Component {
  7. render() {
  8. // highlight-range{1-3,5}
  9. // Use a Provider to pass the current theme to the tree below.
  10. // Any component can read it, no matter how deep it is.
  11. // In this example, we're passing "dark" as the current value.
  12. return (
  13. <ThemeContext.Provider value="dark">
  14. <Toolbar />
  15. </ThemeContext.Provider>
  16. );
  17. }
  18. }
  19. // highlight-range{1,2}
  20. // A component in the middle doesn't have to
  21. // pass the theme down explicitly anymore.
  22. function Toolbar(props) {
  23. return (
  24. <div>
  25. <ThemedButton />
  26. </div>
  27. );
  28. }
  29. class ThemedButton extends React.Component {
  30. // highlight-range{1-3,6}
  31. // Assign a contextType to read the current theme context.
  32. // React will find the closest theme Provider above and use its value.
  33. // In this example, the current theme is "dark".
  34. static contextType = ThemeContext;
  35. render() {
  36. return <Button theme={this.context} />;
  37. }
  38. }

multiple-contexts

  1. // Theme context, default to light theme
  2. const ThemeContext = React.createContext('light');
  3. // Signed-in user context
  4. const UserContext = React.createContext({
  5. name: 'Guest',
  6. });
  7. class App extends React.Component {
  8. render() {
  9. const {signedInUser, theme} = this.props;
  10. // App component that provides initial context values
  11. // highlight-range{2-3,5-6}
  12. return (
  13. <ThemeContext.Provider value={theme}>
  14. <UserContext.Provider value={signedInUser}>
  15. <Layout />
  16. </UserContext.Provider>
  17. </ThemeContext.Provider>
  18. );
  19. }
  20. }
  21. function Layout() {
  22. return (
  23. <div>
  24. <Sidebar />
  25. <Content />
  26. </div>
  27. );
  28. }
  29. // A component may consume multiple contexts
  30. function Content() {
  31. // highlight-range{2-10}
  32. return (
  33. <ThemeContext.Consumer>
  34. {theme => (
  35. <UserContext.Consumer>
  36. {user => (
  37. <ProfilePage user={user} theme={theme} />
  38. )}
  39. </UserContext.Consumer>
  40. )}
  41. </ThemeContext.Consumer>
  42. );
  43. }

theme-detailed

app

  1. import {ThemeContext, themes} from './theme-context';
  2. import ThemedButton from './themed-button';
  3. // An intermediate component that uses the ThemedButton
  4. function Toolbar(props) {
  5. return (
  6. <ThemedButton onClick={props.changeTheme}>
  7. Change Theme
  8. </ThemedButton>
  9. );
  10. }
  11. class App extends React.Component {
  12. constructor(props) {
  13. super(props);
  14. this.state = {
  15. theme: themes.light,
  16. };
  17. this.toggleTheme = () => {
  18. this.setState(state => ({
  19. theme:
  20. state.theme === themes.dark
  21. ? themes.light
  22. : themes.dark,
  23. }));
  24. };
  25. }
  26. render() {
  27. //highlight-range{1-3}
  28. // The ThemedButton button inside the ThemeProvider
  29. // uses the theme from state while the one outside uses
  30. // the default dark theme
  31. //highlight-range{3-5,7}
  32. return (
  33. <Page>
  34. <ThemeContext.Provider value={this.state.theme}>
  35. <Toolbar changeTheme={this.toggleTheme} />
  36. </ThemeContext.Provider>
  37. <Section>
  38. <ThemedButton />
  39. </Section>
  40. </Page>
  41. );
  42. }
  43. }
  44. ReactDOM.render(<App />, document.root);

themed-button

  1. import {ThemeContext} from './theme-context';
  2. class ThemedButton extends React.Component {
  3. // highlight-range{3,12}
  4. render() {
  5. let props = this.props;
  6. let theme = this.context;
  7. return (
  8. <button
  9. {...props}
  10. style={{backgroundColor: theme.background}}
  11. />
  12. );
  13. }
  14. }
  15. ThemedButton.contextType = ThemeContext;
  16. export default ThemedButton;

theme-context

  1. export const themes = {
  2. light: {
  3. foreground: '#000000',
  4. background: '#eeeeee',
  5. },
  6. dark: {
  7. foreground: '#ffffff',
  8. background: '#222222',
  9. },
  10. };
  11. // highlight-range{1-3}
  12. export const ThemeContext = React.createContext(
  13. themes.dark // default value
  14. );

updating-nested

App

  1. import {ThemeContext, themes} from './theme-context';
  2. import ThemeTogglerButton from './theme-toggler-button';
  3. class App extends React.Component {
  4. constructor(props) {
  5. super(props);
  6. this.toggleTheme = () => {
  7. this.setState(state => ({
  8. theme:
  9. state.theme === themes.dark
  10. ? themes.light
  11. : themes.dark,
  12. }));
  13. };
  14. // highlight-range{1-2,5}
  15. // State also contains the updater function so it will
  16. // be passed down into the context provider
  17. this.state = {
  18. theme: themes.light,
  19. toggleTheme: this.toggleTheme,
  20. };
  21. }
  22. render() {
  23. // highlight-range{1,3}
  24. // The entire state is passed to the provider
  25. return (
  26. <ThemeContext.Provider value={this.state}>
  27. <Content />
  28. </ThemeContext.Provider>
  29. );
  30. }
  31. }
  32. function Content() {
  33. return (
  34. <div>
  35. <ThemeTogglerButton />
  36. </div>
  37. );
  38. }
  39. ReactDOM.render(<App />, document.root);

theme-toggler-button

  1. import {ThemeContext} from './theme-context';
  2. function ThemeTogglerButton() {
  3. // highlight-range{1-2,5}
  4. // The Theme Toggler Button receives not only the theme
  5. // but also a toggleTheme function from the context
  6. return (
  7. <ThemeContext.Consumer>
  8. {({theme, toggleTheme}) => (
  9. <button
  10. onClick={toggleTheme}
  11. style={{backgroundColor: theme.background}}>
  12. Toggle Theme
  13. </button>
  14. )}
  15. </ThemeContext.Consumer>
  16. );
  17. }
  18. export default ThemeTogglerButton;

context

  1. // Make sure the shape of the default value passed to
  2. // createContext matches the shape that the consumers expect!
  3. // highlight-range{2-3}
  4. export const ThemeContext = React.createContext({
  5. theme: themes.dark,
  6. toggleTheme: () => {},
  7. });