Context

Context menyediakan cara untuk oper data melalui diagram komponen tanpa harus oper props secara manual di setiap tingkat.

Dalam aplikasi React yang khusus, data dioper dari atas ke bawah (parent ke child) melalui props, tetapi ini bisa menjadi rumit untuk tipe props tertentu (mis. preferensi locale, tema UI) yang dibutuhkan oleh banyak komponen di dalam sebuah aplikasi. Context menyediakan cara untuk berbagi nilai seperti ini di antara komponen tanpa harus oper prop secara explisit melalui setiap tingkatan diagram.

Kapan Menggunakan Context

Context dirancang untuk berbagi data yang dapat dianggap “global” untuk diagram komponen React, seperti pengguna terotentikasi saat ini, tema, atau bahasa yang disukai. Misalnya, dalam kode di bawah ini kita secara manual memasang prop “theme” untuk memberi style pada komponen Button:

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

function Toolbar(props) {
  // komponen Toolbar harus menggunakan *prop* "theme" tambahan  // dan oper ke ThemedButton. Ini bisa menjadi *painful*  // jika setiap tombol di dalam aplikasi perlu mengetahui *theme*-nya  // karena itu harus melewati semua komponen.  return (
    <div>
      <ThemedButton theme={props.theme} />    </div>
  );
}

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

Menggunakan context, kita dapat menghindari mengoper props melalui elemen perantara:

// Context memungkinkan kita untuk oper nilai ke dalam diagram komponen// tanpa secara ekplisit memasukannya ke dalam setiap komponen.// Buat *context* untuk tema saat ini (dengan "light" sebagai default).const ThemeContext = React.createContext('light');
class App extends React.Component {
  render() {
    // Gunakan Provider untuk oper tema saat ini ke diagram di bawah ini.    // Komponen apa pun dapat membacanya, tidak peduli seberapa dalam diagram tersebut.    // Dalam contoh ini, kita mengoper "dark" sebagai nilai saat ini.    return (
      <ThemeContext.Provider value="dark">        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// Komponen di tengah tidak harus // oper temanya secara ekplisit lagi.function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // Tetapkan contextType untuk membaca *context theme* saat ini.  // React akan menemukan Provider *theme* terdekat di atas dan menggunakan nilainya.  // Dalam contoh ini, *theme* saat ini adalah "dark".  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;  }
}

Sebelum Anda Menggunakan Context

Context terutama digunakan ketika beberapa data harus dapat diakses oleh banyak komponen pada tingkat bersarang yang berbeda. Gunakan dengan hemat karena membuat penggunaan kembali komponen menjadi lebih sulit.

Jika Anda hanya ingin menghindari mengoper beberapa props melalui banyak tingkatan, komposisi komponen seringkali menjadi solusi yang lebih sederhana daripada context.

Misalnya, pertimbangkan komponen Page yang mengoper prop user dan avatarSize beberapa tingkat ke bawah sehingga komponen Link dan Avatar yang bersarang dapat membaca prop-nya:

<Page user={user} avatarSize={avatarSize} />
// ... yang *render* ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... yang *render* ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... yang *render* ...
<Link href={user.permalink}>
  <Avatar user={user} size={avatarSize} />
</Link>

Mungkin terasa berlebihan untuk mewariskan props user dan avatarSize melalui banyak tingkatan jika pada akhirnya komponen Avataryang benar-benar membutuhkannya. Ini juga menjengkelkan bahwa setiap kali komponen Avatar membutuhkan lebih banyak props dari atas, Anda harus menambahkannya di semua tingkatan menengah juga.

Salah satu cara untuk mengatasi masalah ini tanpa context adalah dengan mengoper komponen Avatar itu sendiri sehingga komponen perantara tidak perlu tahu tentang props user atau avatarSize:

function Page(props) {
  const user = props.user;
  const userLink = (
    <Link href={user.permalink}>
      <Avatar user={user} size={props.avatarSize} />
    </Link>
  );
  return <PageLayout userLink={userLink} />;
}

// Sekarang, kita memiliki:
<Page user={user} avatarSize={avatarSize} />
// ... yang *render* ...
<PageLayout userLink={...} />
// ... yang *render* ...
<NavigationBar userLink={...} />
// ... yang *render* ...
{props.userLink}

Dengan perubahan ini, hanya komponen Page paling atas yang perlu tahu tentang penggunaan komponen Link dan Avatar oleh user dan avatarSize.

Inversi kontrol ini dapat membuat kode Anda lebih bersih dalam banyak kasus dengan mengurangi jumlah props yang Anda butuhkan untuk melewati aplikasi Anda dan memberikan lebih banyak kontrol ke komponen root. Namun, ini bukan pilihan yang tepat dalam setiap kasus: memindahkan lebih banyak kerumitan lebih tinggi dalam diagram membuat higher-level component lebih rumit dan memaksa lower-level component menjadi lebih fleksibel daripada yang Anda inginkan.

Anda tidak terbatas pada satu child untuk satu komponen. Anda dapat mengoper beberapa children, atau bahkan memiliki beberapa “slot” terpisah untuk children, seperti yang didokumentasikan di sini:

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}
    />
  );
}

Pola ini cukup untuk banyak kasus ketika Anda perlu memisahkan child dari parent terdekatnya. Anda dapat membawanya lebih jauh dengan render props jika child perlu berkomunikasi dengan parent sebelum rendering.

Namun, kadang-kadang data yang sama harus dapat diakses oleh banyak komponen dalam diagram, dan pada tingkat bersarang yang berbeda. Context memungkinkan Anda “menyiarkan” data tersebut, dan mengubahnya, ke semua komponen di bawah. Contoh umum di mana menggunakan context mungkin lebih sederhana daripada alternatif termasuk mengelola locale saat ini, tema, atau cache data.

API

React.createContext

const MyContext = React.createContext(defaultValue);

Buat objek Context. Ketika React render komponen yang menerima objek Context ini akan membaca nilai context saat ini dari pencocokan terdekat Provider di atasnya dalam diagram.

Argumen defaultValue hanya digunakan ketika komponen tidak memiliki Provider yang cocok di atasnya dalam diagram. Ini dapat membantu untuk testing komponen secara terpisah tanpa membungkusnya. Catatan: mengoper undefined sebagai nilai Provider tidak menyebabkan konsumsi komponen menggunakan defaultValue.

Context.Provider

<MyContext.Provider value={/* beberapa nilai */}>

Setiap objek Context dilengkapi dengan komponen Provider React yang memungkinkan komponen konsumsi untuk menerima perubahan context.

Menerima prop value untuk dioper ke komponen konsumsi yang merupakan keturunan Provider ini. Satu Provider dapat dihubungkan ke banyak consumer. Provider dapat disarangkan untuk override nilai lebih dalam di dalam diagram.

Semua consumer yang merupakan keturunan Provider akan render ulang setiap kali prop value dalam Provider berubah. Perambatan dari Provider ke consumer turunannya (termasuk .contextType dan useContext) tidak tunduk ke method shouldComponentUpdate, sehingga consumer diperbarui bahkan ketika komponen leluhur menebus pembaruan tersebut.

Perubahan ditentukan dengan membandingkan nilai-nilai baru dan lama menggunakan algoritma yang sama dengan Object.is.

Catatan

Cara perubahan ditentukan dapat menyebabkan beberapa masalah saat mengoper objek sebagai value: lihat Peringatan.

Class.contextType

class MyClass extends React.Component {
  componentDidMount() {
    let value = this.context;
    /* melakukan efek samping saat *mount* menggunakan nilai MyContext */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /* *render* sesuatu berdasarkan nilai dari MyContext */
  }
}
MyClass.contextType = MyContext;

Properti contextType pada kelas dapat diberikan objek Context yang dibuat oleh React.createContext(). Ini memungkinkan Anda menggunakan nilai saat ini terdekat dari tipe Context menggunakan this.context. Anda dapat merujuk ini dalam salah satu method lifecycle termasuk fungsi render.

Catatan:

Anda hanya bisa menerima satu context menggunakan API ini. Jika Anda perlu membaca lebih dari satu lihat Mengkonsumsi Banyak Contexts.

Jika Anda menggunakan eksperimental sintaksis public class fields, Anda bisa menggunakan class field static untuk menginisialisasi contextType Anda.

class MyClass extends React.Component {
  static contextType = MyContext;
  render() {
    let value = this.context;
    /* *render* sesuatu berdasarkan nilainya */
  }
}

Context.Consumer

<MyContext.Consumer>
  {value => /* render sesuatu berdasarkan nilai *context*-nya */}
</MyContext.Consumer>

Komponen React yang menerima perubahan context. Ini memungkinkan Anda menerima context di dalam komponen fungsi.

Dibutuhkan sebuah fungsi sebagai child. Fungsi menerima nilai context saat ini dan mengembalikkan node React. Argumen value yang diteruskan ke fungsi akan sama dengan prop value dari Provider terdekat untuk context ini di atas dalam diagram. Jika tidak ada Provider untuk context ini di atas, argumen value akan sama dengan defaultValue yang diteruskan ke createContext().

Catatan

Untuk lebih lanjut mengenai pola ‘fungsi sebagai child’, lihat render props.

Context.displayName

Objek Context menerima properti string displayName. React DevTools menggunakan string ini untuk menentukan apa yang harus di tampilkan untuk context tersebut.

Misalnya, komponen berikut ini akan muncul sebagai MyDisplayName di DevTools:

const MyContext = React.createContext(/* beberapa nilai */);
MyContext.displayName = 'MyDisplayName';
<MyContext.Provider> // "MyDisplayName.Provider" in DevTools
<MyContext.Consumer> // "MyDisplayName.Consumer" in DevTools

Contoh

Context Dinamis

Contoh lain yang lebih kompleks dengan nilai dinamis untuk sebuah tema:

theme-context.js

export const themes = {
  light: {
    foreground: '#000000',
    background: '#eeeeee',
  },
  dark: {
    foreground: '#ffffff',
    background: '#222222',
  },
};

export const ThemeContext = React.createContext(  themes.dark // nilai *default*);

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';

// Komponen perantara yang menggunakan 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() {
    // Tombol ThemedButton di dalam ThemeProvider    // menggunakan tema dari *state* sedangkan yang di luar menggunakan    // theme dark default    return (
      <Page>
        <ThemeContext.Provider value={this.state.theme}>          <Toolbar changeTheme={this.toggleTheme} />        </ThemeContext.Provider>        <Section>
          <ThemedButton />        </Section>
      </Page>
    );
  }
}

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

Memperbarui Context Dari Komponen Bersarang

Sering diperlukan untuk memperbarui context dari komponen yang bersarang di suatu tempat dalam diagram komponen. Dalam hal ini Anda oper sebuah fungsi melewati context untuk memungkinkan consumer memperbarui context:

theme-context.js

// Pastikan bentuk nilai default yang di oper ke
// createContext cocok dengan bentuk yang di harapkan *consumer*!
export const ThemeContext = React.createContext({
  theme: themes.dark,  toggleTheme: () => {},});

theme-toggler-button.js

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

function ThemeTogglerButton() {
  // Theme Toggler Button tidak hanya menerima *theme*  // tetapi juga fungsi toggleTheme dari *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 juga berisi fungsi pembaruan yang akan    // diturunkan kedalam *provider context*    this.state = {
      theme: themes.light,
      toggleTheme: this.toggleTheme,    };
  }

  render() {
    // Seluruh *state* diteruskan ke *provider*    return (
      <ThemeContext.Provider value={this.state}>        <Content />
      </ThemeContext.Provider>
    );
  }
}

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

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

Mengonsumsi Banyak Context

Untuk menjaga agar rendering ulang context tetap cepat, React perlu membuat setiap context consumer sebagai node yang terpisah dalam diagram.

// Context Theme, default theme-nya light
const ThemeContext = React.createContext('light');

// Context pengguna yang masuk
const UserContext = React.createContext({
  name: 'Guest',
});

class App extends React.Component {
  render() {
    const {signedInUser, theme} = this.props;

    // Komponen App yang menyediakan nilai *context* awal
    return (
      <ThemeContext.Provider value={theme}>        <UserContext.Provider value={signedInUser}>          <Layout />
        </UserContext.Provider>      </ThemeContext.Provider>    );
  }
}

function Layout() {
  return (
    <div>
      <Sidebar />
      <Content />
    </div>
  );
}

// Komponen dapat menggunakan banyak *context*
function Content() {
  return (
    <ThemeContext.Consumer>      {theme => (        <UserContext.Consumer>          {user => (            <ProfilePage user={user} theme={theme} />          )}        </UserContext.Consumer>      )}    </ThemeContext.Consumer>  );
}

Jika dua atau lebih nilai context sering digunakan bersama, Anda mungkin ingin mempertimbangkan untuk membuat komponen prop render Anda sendiri yang menyediakan keduanya.

Peringatan

Karena context menggunakan identitas referensi untuk menentukan kapan harus render ulang, ada beberapa gotcha yang dapat memicu render yang tidak disengaja dalam consumer ketika parent provider render ulang. Misalnya kode di bawah ini akan render ulang semua consumer setiap kali Provider render ulang karena objek baru selalu dibuat untuk value:

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

Untuk menyiasatinya, angkat nilai ke dalam state induk:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: {something: 'something'},    };
  }

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

API Lawas

Catatan

React sebelumnya dikirimkan dengan API context eksperimental. API lama akan di dukung di semua rilis 16.x, tetapi aplikasi yang menggunakannya harus migrasi ke versi yang baru. API lawas akan dihapus dalam versi major React di masa depan. Baca dokumen context lawas di sini.