好程式設計師web前端分享Context-React跨元件訪問資料的利器

好程式設計師IT發表於2019-11-13

  好程式設計師web 前端分享 Context-React 跨元件訪問資料的利器

  Context 提供了一種跨元件訪問資料的方法。它無需在元件樹間逐層傳遞屬性,也可以方便的訪問其他元件的資料

  在經典的React 應用中,資料是父元件透過 props 向子元件傳遞的。但是在某些特定場合,有些資料需要在各個元件之間共享。 Context 為我們提供一種元件之間共享資料的方式,可以避免資料在元件樹上逐層傳遞

   使用Context 的場合

Context 可以在元件樹的元件之間共享“全域性”資料。例如:登陸的使用者資訊,使用者選擇的主題、語言等等。下面的例子中,我們“手動”自上而下傳遞theme屬性,用來設定Button的樣式。

class App extends React.Component {

  render() {

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

  }

}

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

          >

          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>

    );

  }

}


 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69913892/viewspace-2663962/,如需轉載,請註明出處,否則將追究法律責任。

相關文章