Context

Context, prop’ları her seviyede manuel olarak geçmek zorunda kalmadan bileşen ağacı üzerinden veri iletmenin bir yolunu sağlar.

Tipik bir React uygulamasında veri prop’lar aracılığıyla yukarıdan aşağıya aktarılır (üst bileşenlerden alt bileşenlere), fakat bu bir uygulamada birçok bileşene ihtiyaç duyulan belirli tipteki prop’lar (örneğin; lokalizasyon, arayüz teması) için kullanışsız olabilir. Context, bileşen ağacın her bir seviyesi üzerinden açıkça bir prop geçirmeden, bileşenler arasında bu gibi değerleri paylaşmanın bir yolunu sağlar.

Context Ne Zaman Kullanılır

Context; mevcut kullanıcıyı doğrulama, tema veya dil seçimi gibi React bileşen ağacında global olarak düşünülebilecek verileri paylaşmak için tasarlanmıştır. Örneğin aşağıdaki kodda Button bileşenine stil vermek için manuel olarak bir “theme” prop’unu geçiyoruz.

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

function Toolbar(props) {
  // Toolbar bileşeninin ek bir "theme" prop'u alması gerekir
  // ve onu ThemeButton'a geçirmelidir. Uygulamadaki her bir
  // button'un theme'yi bilmesi gerekiyorsa zor olabilir,
  // çünkü her bileşenden geçmesi gerekecek.
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  );
}

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

Context kullanarak, prop’ları ara öğelerden geçirmekten kaçınabiliriz.

// Context her bir bileşenin içinden açıkça geçmeden, 
// bileşen ağacının derinliklerine bir value geçmemizi sağlar.
// Mevcut theme için bir context oluştur (varsayılan olarak "light" ile).
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // Geçerli temayı aşağıdaki ağaca taşımak için bir Provider kullanın.
    // Herhangi bir bileşen ne kadar derinde olursa olsun okuyabilir.
    // Bu örnekte, mevcut değer olarak "dark" geçiyoruz.
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// Aradaki bir bileşen artık temayı açıkça aşağı aktarmak zorunda değil.
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // Mevcut tema context'ini okumak için bir contextType atayın.
  // React, en yakın tema Provider'ı bulacak ve değerini kullanacak.
  // Bu örnekte mevcut tema "dark"tır.
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

Context Kullanmadan Önce

Context esas olarak, bazı verilerin farklı düzeylerdeki iç içe geçmiş birçok bileşen tarafından erişilebilir olması gerektiğinde kullanılır. Bileşenin yeniden kullanımını zorlaştırdığından onu ölçülü bir şekilde uygulayın.

Yalnızca bazı prop’ları birçok aşama üzerinden geçmek istemezseniz, bileşen kompozisyonu genellikle Context’ten daha basit bir çözümdür.

Örneğin, derinlemesine iç içe geçmiş Link ve Avatar bileşenlerinin okuyabilmesi için avatarSize ve user prop’larını birkaç seviye aşağıya aktaran bir Page bileşeni düşünün:

<Page user={user} avatarSize={avatarSize} />
// ... Bu, bunu render ediyor ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... Bu, bunu render ediyor ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... Bu, bunu render ediyor ...
<Link href={user.permalink}>
  <Avatar user={user} size={avatarSize} />
</Link>

Sonunda sadece Avatar bileşeni ihtiyaç duyuyorsa, user ve avatarSize ‘ın birçok seviyeden geçmesi gereksiz olabilir. Ayrıca Avatar bileşeni yukarıdan daha fazla prop’a ihtiyaç duyduğunda, bu prop’ları tüm ara seviyelerde de eklemeniz gerekir.

Bu sorunu Context’siz çözmenin bir yolu Avatar bileşeninin kendisinin prop olarak geçilmesidir, böylece ara bileşenlerin user ve avatarSize prop’ları hakkında bilgi sahibi olması gerekmez:

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

// Şimdi, bizde olan:
<Page user={user} avatarSize={avatarSize} />
// ... Bu, bunu render ediyor ...
<PageLayout userLink={...} />
// ... Bu, bunu render ediyor ...
<NavigationBar userLink={...} />
// ... Bu, bunu render ediyor ...
{props.userLink}

Bu değişiklikle birlikte sadece en üstteki Page bileşeni Link ve Avatar bileşenlerinin user ve avatarSize kullanımını bilmesi gerekir.

Bu kontrolün tersine çevrilmesi, birçok durumda uygulamanızdan geçirmeniz gereken prop’ların sayısını azaltarak ve kök bileşenlere daha fazla kontrol sağlayarak kodunuzu daha temiz hale getirebilir. Fakat bu her durumda doğru bir seçim değildir: bileşen ağacında daha fazla karmaşıklık taşımak, daha üst seviyeli bileşenleri daha karmaşık hale getirir ve daha düşük seviyeli bileşenleri istediğinizden daha esnek olmaya zorlar.

Bir bileşen için tek bir alt elemanla sınırlı değilsiniz. Burada belirtildiği gibi, alt elemanlar için birden çok alt eleman geçirebilirsiniz, hatta alt bileşenler için birden fazla ayrı “slots’a” sahip olabilirsiniz.

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

Bu model, bir alt elemanı üst elemanlarından ayırmanız gerektiğinde çoğu durum için yeterlidir. Alt elemanın render olmadan önce üst eleman ile iletişim kurması gerekiyorsa, bunu render prop’larla daha ileriye götürebilirsin.

Fakat, bazen aynı verinin ağaçtaki birçok bileşen tarafından ve farklı iç içe geçmiş seviyelerinde erişilebilir olması gerekir. Context, bu tur verileri ve güncellemeleri ağaçtaki tüm bileşenlere “yaymanızı” sağlar. Context kullanımının diğer alternatiflerden daha basit olabileceği ortak örnekler arasında konum ayarlarının yönetimi, tema veya veri önbelleği bulunur.

API

React.createContext

const MyContext = React.createContext(defaultValue);

Bir Context nesnesi oluşturur. React, bu Context nesnesine abone bir bileşen oluşturduğunda, context’in mevcut değerini ağaçtaki en yakın Provider'dan okuyacaktır.

defaultValue argümanı yalnızca, bir bileşenin üstünde ağaçta eşleşen bir Provider bulunmadığında kullanılır. Bu, bileşenleri başka bileşenlerin altına koymadan izole bir şekilde test etmek için yardımcı olabilir. Not: Provider value değerini undefined geçmek tüketici bileşenlerinin defaultValue kullanmasına neden olmaz.

Context.Provider

<MyContext.Provider value={/* bir değer */}>

Her Context nesnesi, tüketici bileşenlerin context güncellemelerine abone olmasını sağlayan bir React Provider bileşeni ile birlikte gelir.

Bu Provider’ın soyundan gelen tüketici bileşenlerine geçirilecek olan bir value prop’u kabul eder. Birçok tüketici bir Provider’a bağlanabilir. Provider’lar ağaçtaki daha derin değerleri değiştirmek için iç içe geçirilebilirler.

Bir Provider’ın soyundan gelen tüm tüketiciler, Provider’ın value prop’u her değiştiğinde yeniden oluşturulur. Provider’ın soyundan gelen tüketicilere yayılması, shouldComponentUpdate metoduna tabi değildir, dolayısıyla herhangi bir bileşen güncellemeyi önlediğinde bile tüketici güncellenir.

Object.is gibi aynı algoritma kullanılarak yeni ve eski değerler karşılaştırıp değişiklikler belirlenir.

Not

Değişiklikleri belirlerken nesneleri value olarak geçmek bazı sorunlara neden olabilir: bakınız Uyarılar.

Class.contextType

class MyClass extends React.Component {
  componentDidMount() {
    let value = this.context;
    /* Mycontext değerini kullanarak mount'da yan etki yapma */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /* Mycontext değerini esas alarak bir şey render etme */
  }
}
MyClass.contextType = MyContext;

Bir sınıftaki contextType özelliğine React.createContext() tarafından oluşturulan bir Context nesnesi atanabilir. Bu, this.context ‘i kullanarak bu Context türünün en yakın mevcut değerini kullanmanızı sağlar. Bunu render metodu da dahil olmak üzere yaşam döngüsü metodlarından herhangi birinde belirtebilirsiniz.

Not:

Bu API’yi kullanarak yalnızca tek bir context’e abone olabilirsiniz. Daha fazla okumanız gerekiyorsa, Çoklu Context Tüketimi kısmına bakabilirsiniz.

Deneysel public sınıf alanları sözdizimini (public class fields) kullanıyorsanız, contextType'ınızı başlatmak için statik bir sınıf alanı kullanablirsiniz.

class MyClass extends React.Component {
  static contextType = MyContext;
  render() {
    let value = this.context;
    /* value değerine dayalı bir şey render etmek */
  }
}

Context.Consumer

<MyContext.Consumer>
  {value => /* context değerine dayalı bir şey render etme */}
</MyContext.Consumer>

Context değişikliklerine abone olan bir React bileşeni. Bu, bir fonksiyon bileşen içindeki bir context’e abone olmanıza izin verir.

Alt eleman olarak fonksiyon verilmesine ihtiyaç duyar. Fonksiyon geçerli context değerini alır ve bir React düğümü döndürür. Fonksiyona iletilen value argümanı, yukarıda bu context için ağaçta en yakın Provider’ın value prop’una eşit olacaktır. Yukarıdaki bu context için Provider yoksa, value argümanı createContext() öğesine iletilmiş defaultValue değerine eşit olur.

Not

Alt eleman olarak fonksiyon modeline dair daha fazla bilgi için, bakınız: prop’ları renderlamak.

Context.displayName

Context objeleri displayName adında bir string property kabul eder. React DevTools context için ne göstereceğine karar vermek için bu stringi kullanır.

Örneğin, aşağıdaki bileşen DevTools’da MyDisplayName olarak gözükecektir.

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

<MyContext.Provider> // "MyDisplayName.Provider" in DevTools
<MyContext.Consumer> // "MyDisplayName.Consumer" in DevTools

Örnekler

Dinamik Context

Tema için dinamik değerli çok karmaşık bir örnek:

theme-context.js

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

export const ThemeContext = React.createContext(
  themes.dark // varsayılan değer
);

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

// ThemedButton'u kullanan bir ara bileşen
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 içinde bulunan ThemedButton
    // tema bilgisini state'ten alır. Dışarıda bulunan ise
    // varsayılan temayı kullanır.
    return (
      <Page>
        <ThemeContext.Provider value={this.state.theme}>
          <Toolbar changeTheme={this.toggleTheme} />
        </ThemeContext.Provider>
        <Section>
          <ThemedButton />
        </Section>
      </Page>
    );
  }
}

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

İç İçe Geçmiş Bileşenden Context Güncelleme

Context’i bileşen ağacında derinlere yerleştirilmiş bir bileşenden genellikle güncellemek gerekir. Bu durumda, tüketicilerin context’i güncellemesine izin vermek için context’den bir method’u aşağıya iletebilirsiniz:

theme-context.js

// CreateContext öğesine iletilen varsayılan değerin formunun,
// tüketicilerin beklediği formla eşleştiğinden emin olun!
export const ThemeContext = React.createContext({
  theme: themes.dark,
  toggleTheme: () => {},
});

theme-toggler-button.js

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

function ThemeTogglerButton() {
  // Tema Değiştirme Düğmesi yalnızca temayı değil, 
  // aynı zamanda context'ten bir toggleTheme methodu alır
  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 ayrıca güncelleme işlevini içerir, 
    // böylece context provider'a aktarılır.
    this.state = {
      theme: themes.light,
      toggleTheme: this.toggleTheme,
    };
  }

  render() {
    // Tüm state provider'a iletilir.
    return (
      <ThemeContext.Provider value={this.state}>
        <Content />
      </ThemeContext.Provider>
    );
  }
}

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

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

Çoklu Context’leri Kullanma

Context’in yeniden render edilmesini hızlı tutmak için React her context tüketiciyi ağaçta ayrı bir düğüm haline getirmelidir.

// Tema context'i, varsayılan olarak light tema
const ThemeContext = React.createContext('light');

// Oturum açmış kullanıcı context'i
const UserContext = React.createContext({
  name: 'Guest',
});

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

    // İlk context value'larını sağlayan App bileşeni
    return (
      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Layout />
        </UserContext.Provider>
      </ThemeContext.Provider>
    );
  }
}

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

// Bir bileşen birden fazla context'i tüketebilir
function Content() {
  return (
    <ThemeContext.Consumer>
      {theme => (
        <UserContext.Consumer>
          {user => (
            <ProfilePage user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>
  );
}

İki veya daha fazla context değerleri sıklıkla birlikte kullanılıyorsa her ikisini de sağlayan kendi render prop bileşeninizi oluşturmayı düşünmek isteyebilirsiniz.

Uyarılar

Context, yeniden render edilme zamanını belirlemek için referans kimliği kullandığından, bir Provider’ın üst elemanı yeniden render’landığında tüketicilerdeki istenmeyen render’ları tetikleyebilecek bazı kazanımlar vardır. Örneğin aşağıdaki kodda provider her yeniden render’landığında tüm tüketiciler yeniden render’lanır. Çünkü value için her zaman yeni bir obje oluşturulur:

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

Bunu aşmak için, value değerini üst elemanın state’ine taşıyın:

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

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

Eski Sürüm API

Not

React daha önce deneysel bir context API ile yayınlanmıştı. Eski API tüm 16.x sürümlerinde desteklenecek ancak onu kullanan uygulamalar yeni sürüme geçmelidir. Eski sürüm API’ler önümüzdeki ana React versiyonlarından kaldırılacaktır. Eski sürüm Context dökümanlarını buradan okuyun.