首頁  >  文章  >  web前端  >  react中context是什麼

react中context是什麼

WBOY
WBOY原創
2022-06-28 10:55:372467瀏覽

在react中,context是一種無需為每層組件手動添加props就能在組件樹之間進行資料傳遞的方法;context提供了一種在組件之間共享指定值的方式,而且不必顯式的通過組件樹的逐層傳遞props。

react中context是什麼

本教學操作環境:Windows10系統、react17.0.1版、Dell G3電腦。

react中context是什麼

Context 提供了一個不需要為每層元件手動新增 props,就能在元件樹間進行資料傳遞的方法。在典型的React 應用程式中,資料是透過props 屬性自上而下(由父及子)進行傳遞的,但這種做法對於某些類型的屬性而言是極其繁瑣的(例如:地區偏好,UI主題),這些屬性是應用程式中許多元件都需要的。 Context 提供了一種在元件之間共用此類值的方式,而不必明確地透過元件樹的逐層傳遞 props。

Context 什麼時候用?

Context 設計目的是為了共享那些對於一個元件樹而言是「全域」的數據,例如當前認證的使用者、主題或首選語言。舉個例子,在下面的程式碼中,我們透過一個「theme」 屬性手動調整一個按鈕元件的樣式

class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}

function Toolbar(props) {
  // Toolbar 组件接受一个额外的“theme”属性,然后传递给 ThemedButton 组件。
  // 如果应用中每一个单独的按钮都需要知道 theme 的值,这会是件很麻烦的事,
  // 因为必须将这个值层层传递所有组件。
  return (
    <p>
      <ThemedButton theme={props.theme} />
    </p>
  );
}

class ThemedButton extends React.Component {
  render() {
    return <Button theme={this.props.theme} />;
  }
}

// 通过props传递:App -> Toolbar -> ThemedButton
// 如果嵌套很深,那么需要逐层传递props,即使中间不需要该props,显得很繁琐

使用context, 我們可以避免透過中間元素傳遞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 (
    <p>
      <ThemedButton />
    </p>
  );
}

class ThemedButton extends React.Component {
  // 指定 contextType 读取当前的 theme context。
  // React 会往上找到最近的 theme Provider,然后使用它的值。
  // 在这个例子中,当前的 theme 值为 “dark”。
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}
// 也可以使用 ThemedButto.contextType = ThemeContext;

#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={/* 某个值 */}>

每個 Context 物件都會傳回一個 Provider React 元件,它允許消費元件訂閱 context 的變更。

Provider 接收一個 value 屬性,傳遞給消費元件。一個 Provider 可以和多個消費元件有對應關係。多個 Provider 也可以嵌套使用,裡層的會覆蓋外層的資料。

當 Provider 的 value 值改變時,它內部的所有消費元件都會重新渲染。 Provider 及其內部 consumer 元件都不受制於 shouldComponentUpdate 函數,因此當 consumer 元件在其祖先元件退出更新的情況下也能更新。

Class.contextType

##掛載在class 上的 

contextType 屬性會被重賦值為一個由 React.createContext() 所建立的Context對象。這能讓你使用 this.context 來消費最近 Context 上的那個價值。你可以在任何生命週期中存取到它,包括 render 函數中

import MyContext from './MyContext';

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 组件的值进行渲染 */
  }
  // 或者如上边例子一样使用 static contextType = MyContext;
}
MyClass.contextType = MyContext;

Context.Consumer
import MyContext from './MyContext';

function ToolList() {
  return (
    <MyContext.Consumer
      {value => /* 基于 context 值进行渲染*/}
    </MyContext.Consumer>
  )
}

這裡,React 元件也可以訂閱到 context 變更。這能讓你在函數式元件中完成訂閱 context。

這需要函數作為子元素(function as a child)這種做法。這個函數接收目前的 context 值,並傳回一個 React 節點。傳給函數的 

value 值等同於往上元件樹離這個 context 最近的 Provider 所提供的 value 值。如果沒有對應的 Provider,value 參數等同於傳給 createContext() 的 defaultValue

Context.displayName

context 物件接受一個名為 

displayName 的 property,類型為字串。 React DevTools 使用該字串來決定 context 要顯示的內容。

如下述元件在DevTools 中會顯示為MyDisplayName

const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';

<MyContext.Provider>   // "MyDisplayName.Provider" 在 DevTools 中
<MyContext.Consumer>   // "MyDisplayName.Consumer" 在 DevTools 中

範例

##動態Context

對於上面的theme 例子,使用動態值(dynamic values)後更複雜的用法

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;
    // 获取到ThemeContext中的默认值
    let theme = this.context;
    return (
      <button
        {...props}
        style={{backgroundColor: theme.background}}
      />
    );
  }
  // static contextType = ThemeContext;
}
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);

// 使用ThemeContext.Provider包裹的组件,可以消费到ThemeContext中的value
// 即Toolbar、ThemedButton中都可以使用this.context来获取到value
// 注意观察,更新state的方法是通过props向下传递,由子孙组件触发更新,下面会讲到通过context的方式传递更新函数

在嵌套组件中更新 Context

在上面的例子中,我们通过 props 的方式向下传递一个更新函数,从而改变 App 中 themes 的值。我们知道,从一个在组件树中嵌套很深的组件中更新 context 是很有必要的。在这种场景下,你可以通过 context 传递一个函数,使得 consumers 组件更新 context

theme-context.js

// 确保传递给 createContext 的默认值数据结构是调用的组件(consumers)所能匹配的!
export const ThemeContext = React.createContext({
  theme: themes.dark,
  toggleTheme: () => {},   // 定义更新主题的方法,向下传递
});

theme-toggler-button.js

import { ThemeContext } from './theme-context';

function ThemeTogglerButton() {
  // Theme Toggler 按钮不仅仅只获取 theme 值,它也从 context 中获取到一个 toggleTheme 函数(下面app.js部分)
  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,   // 定义更新函数,通过context方式向下传递
    };
  }

  render() {
    // 整个 state 都被传递进 provider
    return (
      <ThemeContext.Provider value={this.state}>
        <Content />
      </ThemeContext.Provider>
    );
  }
}

function Content() {
  return (
    <p>
      <ThemeTogglerButton />
    </p>
  );
}

ReactDOM.render(<App />, document.root);

消费多个 Context

为了确保 context 快速进行重渲染,React 需要使每一个 consumers 组件的 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 (
    <p>
      <Sidebar />
      <Content />
    </p>
  );
}

// 一个组件可能会消费多个 context
function Content() {
  return (
    <ThemeContext.Consumer>
      {theme => (
        <UserContext.Consumer>
          {user => (
            <ProfilePage user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>
  );
}

如果两个或者更多的 context 值经常被一起使用,那你可能要考虑一下另外创建你自己的渲染组件,以提供这些值。

注意事项

因为 context 会使用参考标识(reference identity)来决定何时进行渲染,这里可能会有一些陷阱,当 provider 的父组件进行重渲染时,可能会在 consumers 组件中触发意外的渲染。举个例子,当每一次 Provider 重渲染时,以下的代码会重渲染所有下面的 consumers 组件,因为 value 属性总是被赋值为新的对象

class App extends React.Component {
  render() {
    return (
      <MyContext.Provider value={{something: &#39;something&#39;}}>
        <Toolbar />
      </MyContext.Provider>
    );
  }
}

为了防止这种情况,将 value 状态提升到父节点的 state 里

class App extends React.Component {
  constructor(props) {
    super(props);
    // 多次渲染,state 会被保留,当value不变时,下面的 consumers 组件不会重新渲染 
    this.state = {
      value: {something: 'something'},
    };
  }

  render() {
    return (
      <Provider value={this.state.value}>
        <Toolbar />
      </Provider>
    );
  }
}

【相关推荐:javascript视频教程web前端

以上是react中context是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn