好程序員web前端分享Context-React跨組件訪問數據的利器

  好程序員web前端分享Context-React跨組件訪問數據的利器前端

  Context提供了一種跨組件訪問數據的方法。它無需在組件樹間逐層傳遞屬性,也能夠方便的訪問其餘組件的數據node

  在經典的React應用中,數據是父組件經過props向子組件傳遞的。可是在某些特定場合,有些數據須要在各個組件之間共享。Context爲咱們提供一種組件之間共享數據的方式,能夠避免數據在組件樹上逐層傳遞程序員

  使用Context的場合web

Context能夠在組件樹的組件之間共享「全局」數據。例如:登錄的用戶信息,用戶選擇的主題、語言等等。下面的例子中,咱們「手動」自上而下傳遞theme屬性,用來設定Button的樣式。算法

class App extends React.Component {數組

  render() {app

    return <Toolbar theme="dark"></Toolbar>;ide

  }函數

}this

function Toolbar(props) {

  // The Toolbar component must take an extra "theme" prop

  // and pass it to the ThemedButton. This can become painful

  // if every single button in the app needs to know the theme

  // because it would have to be passed through all components.

  return (

    <div>

      <ThemedButton theme={props.theme}></ThemedButton>

    </div>

  );

}

class ThemedButton extends React.Component {

  render() {

    return <Button theme={this.props.theme}></Button>;

  }

}

使用Context,咱們能夠避免經過多箇中間組件傳遞props

// Context lets us pass a value deep into the component tree// without explicitly threading it through every component.// Create a context for the current theme (with "light" as the default).const ThemeContext = React.createContext('light');

class App extends React.Component {

  render() {

    // Use a Provider to pass the current theme to the tree below.

    // Any component can read it, no matter how deep it is.

    // In this example, we're passing "dark" as the current value.

    return (

      <ThemeContext.Provider value="dark">

        <Toolbar></Toolbar>

      </ThemeContext.Provider>

    );

  }

}

 

// A component in the middle doesn't have to

// pass the theme down explicitly anymore.

function Toolbar(props) {

  return (

    <div>

      <ThemedButton />

    </div>

  );

}

 

class ThemedButton extends React.Component {

  // Assign a contextType to read the current theme context.

  // React will find the closest theme Provider above and use its value.

  // In this example, the current theme is "dark".

  static contextType = ThemeContext;

  render() {

    return <Button theme={this.context} />;

  }

}

有時候,有些數據須要被不少組件訪問,並且這些組件在組件樹的不一樣層上。Context可使咱們以「廣播」的形式,在各個組件中共享數據的改變

Context相關API

React.createContext

const MyContext = React.createContext(defaultValue);複製代碼

建立一個新的Context對象。當React渲染一個組件,且該組件註冊了Context時,它將讀取父組件中,距離該組件最近的Provider組件的Context

defaultValue只有在「Consumer」組件找不到Provider組件時,纔會被使用。

Context.Provider

<MyContext.Provider value={/* some value */}>複製代碼

每一個Context對象都攜帶一個名叫Provider的React組件。Provider可使得「Consumer」組件監聽context的變動

經過向Provider的後代Consumer組件傳遞value的prop,一個Provider能夠與多個Consumer組件創建聯繫。

全部的後代Consumer組件在Provider的value屬性更新後,都會被從新渲染。這個更新從Provider到其後代Consumer組件之間傳播,可是並不會觸發shouldComponentUpdate方法。因此即便Consumer組件的祖先組件沒有更新,Consumer組件也會更新

Context使用與Object.is相同的算法來對比value的新、舊值,以斷定其value是否被更新了

注意

當向value傳遞對象時,這種斷定value是否改變的方式可能會引發問題。

Class.contextType

class MyClass extends React.Component {

  componentDidMount() {

    let value = this.context;

    /* perform a side-effect at mount using the value of MyContext */

  }

  componentDidUpdate() {

    let value = this.context;

    /* ... */

  }

  componentWillUnmount() {

    let value = this.context;

    /* ... */

  }

  render() {

    let value = this.context;

    /* render something based on the value of MyContext */

  }

}

MyClass.contextType = MyContext;

爲class的contextTpe屬性賦值一個Context對象後,咱們能夠經過this.context在組件的各個聲明周期函數中獲取到當前的Context對象的方法

注意:

經過這種方式,每一個組件只能註冊一個context對象。若是須要讀取多個context的value值

若是編碼中使用了ES實驗中的語法,那麼可使用類的靜態(static)成員來初始化contextTYpe.代碼以下:

class MyClass extends React.Component {

 static contextType = MyContext;

 render() {

   let value = this.context;

   /* render something based on the value */

 }

}

Context.Consumer

<MyContext.Consumer>

  {value => /* render something based on the context value */}

</MyContext.Consumer>

Consumer是一個監聽context變化的React組件。它使得咱們能夠在一個函數組件中,監聽contxt的改變。

Consumer組件要求其子元素爲一個函數。該函數的參數接收當前的context的value值,要求返回一個React節點(node) 傳遞給該函數的參數value等於距離此Consumner最近的外層Provider組件的context值。若是沒有外層的Provider組件,則等於調用createContext()時傳遞的參數值(context的默認值)。

注意

更多關於「子元素爲一個函數」的信息

栗子

在嵌套組件中更新Context

開發中,咱們常常須要在某些嵌套結構很深的組件上更新context的value值。此時,咱們能夠向下傳遞一個函數,用它來更新context的value。代碼以下:

theme-context.js

// Make sure the shape of the default value passed to// createContext matches the shape that the consumers expect!export const ThemeContext = React.createContext({

  theme: themes.dark,

  toggleTheme: () => {},

});

theme-toggler-button.js

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

function ThemeTogglerButton() {

  // The Theme Toggler Button receives not only the theme

  // but also a toggleTheme function from the context

  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 also contains the updater function so it will

    // be passed down into the context provider

    this.state = {

      theme: themes.light,

      toggleTheme: this.toggleTheme,

    };

  }

 

  render() {

    // The entire state is passed to the provider

    return (

      <ThemeContext.Provider value={this.state}>

        <Content />

      </ThemeContext.Provider>

    );

  }

}

 

function Content() {

  return (

    <div>

      <ThemeTogglerButton />

    </div>

  );

}

 

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

使用多個Contexts

爲了保持React的快速渲染,咱們須要將每一個consumer組件編寫成一個獨立的組件節點(node)

// Theme context, default to light themeconst ThemeContext = React.createContext('light');

// Signed-in user contextconst UserContext = React.createContext({

  name: 'Guest',

});

class App extends React.Component {

  render() {

    const {signedInUser, theme} = this.props;

 

    // App component that provides initial context values

    return (

      <ThemeContext.Provider value={theme}>

        <UserContext.Provider value={signedInUser}>

          <Layout />

        </UserContext.Provider>

      </ThemeContext.Provider>

    );

  }

}

 

function Layout() {

  return (

    <div>

      <Sidebar />

      <Content />

    </div>

  );

}

 

// A component may consume multiple contexts

function Content() {

  return (

    <ThemeContext.Consumer>

      {theme => (

        <UserContext.Consumer>

          {user => (

            <ProfilePage user={user} theme={theme} />

          )}

        </UserContext.Consumer>

      )}

    </ThemeContext.Consumer>

  );

}

若是有兩個以上的context常常一塊兒使用,咱們須要考慮建立一個render prop component一併提供兩個Context

注意

由於context使用引用標示符(reference identity)來判斷什麼時候須要從新渲染,因此有些狀況下,當provider的父元素從新渲染時,會觸發consumer的非內部渲染。例以下面代碼,在每次Provider從新渲染時,會從新渲染全部的consumer組件。由於會一直建立一個新的對象賦值給value(value一直在變)

class App extends React.Component {

  render() {

    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>

    );

  }

}

相關文章
相關標籤/搜索