Kontekst

Kontekst məlumatları komponent ağacının hər səviyyəsində proplar göndərmədən məlumatları komponentlərə ötürməyə təmin edir.

Standart React applikasiyasında, məlumat yuxarıdan aşağı (valideyndən uşağa) proplar vasitəsi ilə göndərilir. Lakin bir çox komponentin istifadə etdiyi bəzi prop tiplərinin (məsələn dil seçimi, UI şablonun) göndərilməsi çox yorucu və çətin ola bilər. Kontekst, dəyərlərin komponentlər arasında, komponent ağacının hər səviyyəsində prop göndərmədən paylaşmasına yol göstərir.

Nə Zaman Kontekst İşlətmək Lazımdır

Kontekst “qlobal” qəbul olunan məlumatları (məsələn avtorizasiya olunmuş istifadəçi, şablon, və ya seçilmiş dil) React komponentlər ağacında paylaşmaq üçün nəzərdə tutulmuşdur. Məsələn, aşağıdakı kodda biz Button komponentini dəyişmək üçün “theme” propunu bütün komponentlərdən bir-bir göndəririk:

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

function Toolbar(props) {
  // Toolbar komponenti əlavə "theme" propu qəbul etməli və
  // bu propu ThemedButton komponentinə göndərməlidir. Applikasiyada hər bir
  // düymənin şablondan xəbəri olması üçün, "theme" propunu bütün
  // komponentlərdən keçmirmək çox yorucu ola bilər.
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  );
}

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

Kontekst işlətdikdə isə, biz propları ara elementlərdən göndərməyə bilərik:

// Kontekst dəyərləri komponent ağacında hər komponentdən
// keçmədən lazım olan komponentə ötürməyə icazə verir.
// Cari şablon üçün yeni bir kontekst yaradın ("light" default dəyər kimi).
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // Provider-dən istifadə edərək cari şablon dəyərini aşağıdakı ağaca göndərin.
    // Dərinlikdən asılı olmayaraq, hər hansı bir komponent bu dəyəri oxuya bilər.
    // Bu misalda, biz "dark" yazısını cari dəyər kimi göndəririk.
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// Ortadakı komponent artıq
// şablon dəyərini açıq şəkildə göndərməməlidir.
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // Cari şablon dəyərini kontekstdən oxumaq üçün contextType təyin edin.
  // React ən yaxın olan Şablon Provider-ini tapıb kontekstin dəyərindən istifadə edəcək.
  // Bu misalda, cari şablonun dəyəri "dark"-dır.
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

Kontekst İşlətməzdən Əvvəl

Kontekst əsasən məlumatın fərqli səviyyələrdə yerləşən bir neçə komponent tərəfindən işlənməsi üçündür. Bunu hər yerdə işlətməyin, çünki komponentin yenidən istifadəsini çətinləşdirir.

Əgər siz propları bir neçə səviyyədən göndərmək istəmirsinizsə, komponent kompozisiyası kontekstdən daha sadə həlldir.

Misal üçün, gəlin useravatarSize proplarını mövcud olan Page komponentindən bir neçə səviyyədə göndərək ki, dərində olan LinkAvatar komponentləri bu propları oxuya bilsinlər:

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

Əgər useravatarSize propları yalnız Avatar komponenti tərəfindən işlənəcəksə, bu propların bütün səviyyələrdə göndərilməsi lazımsızdır. Əgər Avatar komponentinə yeni proplar lazım olsa, siz yenə lazım olan propları bütün ara komponentlərdən keçirməlisiniz.

Bu problemi kontekstsiz həll etməyin yolu Avatar komponentinin özünü göndərməkdir. Bu zaman, ara komponentlərin useravatarSize proplarını bilməsi lazım deyil:

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

// İndi bizdə aşağıdakı var
<Page user={user} avatarSize={avatarSize} />
// ... render edir ...
<PageLayout userLink={...} />
// ... render edir ...
<NavigationBar userLink={...} />
// ... render edir ...
{props.userLink}

Bu dəyişiklik ilə, yalnız ən yuxarıda olan Page komponenti, LinkAvatar komponentlərinin useravatarSize proplarını işlətməyini bilir.

Bu kontrolun inversiyası applikasiyada göndərilən propların sayını azaldaraq və ana komponentlərə daha çox kontrol verərək bir neçə ssenaridə kodunuzu daha təmiz edir. Lakin, bu metod hər ssenari üçün düzgün seçim deyil: mürəkkəbiliyi komponent ağacında yüksək olan komponentlərə köçürdükdə, yuxarı səviyyəli komponentləri daha çətinləşir və aşağı səviyyəli komponentlərin daha əyilgən olması lazım olur.

Siz komponentdə bir uşaq göstərməyə məhdud deyilsiniz. Siz bir neçə uşağı, hətta uşaqlar üçün bir neçə “yuva da” edə bilərsiniz (burada sənələşmişdir):

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 metod uşaqları yaxın valideyndən ayırmaq üçün kifayətdir. Əgər uşaq komponent, render etməzdən öncə valideyn ilə kommunikasiya qurmalıdırsa, siz render proplardan istifadə edə bilərsiniz.

Lakin, bəzən eyni məlumatlar komponent ağacında fərqli səviyyələrdə bir neçə komponent tərəfindən işlədilə bilməlidirlər. Kontekst belə məlumatları və bu məlumatlarda olan dəyişiklikləri, bütün aşağısında olan komponentlərə “yayımlaya” bilir. Bəzi nümunələrdə kontekst işlətmək alternativlərdən daha sadə ola bilər (dilin seçimi, şablon, məlumat keşi).

API

React.createContext

const MyContext = React.createContext(defaultValue);

Kontekst obyekti yaradır. React, Context obyektinə “abunə olan” komponentləri render edərkən, yuxarı səviyyədə ən yaxın olan Provider-dən cari kontekst dəyərini oxuyacaq.

defaultValue arqumenti yalnız komponentin yuxarı səviyyəsində Provider olmadığı zaman işlənir. Bu dəyər, komponentləri Provider ilə əhatə etmədən, ayrılıqda test etmək üçün faydalıdır. Qeyd: Provider dəyərinə undefined göndərildikdə, qoşulan komponentlər defaultValue-dan istifadə etmirlər.

Context.Provider

<MyContext.Provider value={/* bir dəyər */}>

Hər bir Context obyekti Provider adlı React komponenti ilə gəlir. Bu komponent kontekstdə olan dəyişikliklərə abunə olmaq istəyən komponentlərə imkan yaradır.

Provayder value propu qəbul edir. Bu propun dəyəri abunə olan komponentlərə ötürülür. Bir Provider bir neçə Consumer komponentə goşula bilər. Provayderlər eyni komponent ağacında bir neçə səviyyədə ola bilər. Ağacda dərində yerləşən provayderlər, yuxarıda olan provayderlərin dəyərlərini əvəz edir.

Provayderin aşağısında olan bütün istehlakçılar, Provayderin value propu dəyişdikdə yenidən render edir. Provayderdən aşağıya məlumatların yayınlaması, shouldComponentUpdate funksiyasından asılı deyil. Bu deməkdir ki, yuxarı komponentdə heç bir komponent yenilənməsə belə Provider-ə abunə olan komponent yenilənəcək.

Dəyişikliklər yeni və köhnə dəyərlərin Object.is alqoritminə bənzər bir alqoritm ilə müqayisəsi ilə təyin olunur.

Qeyd

Dəyişikliklər value-a obyekti göndərdikdə problem yarada bilərlər: Dəyişikliklər bölməsinə baxın.

Class.contextType

class MyClass extends React.Component {
  componentDidMount() {
    let value = this.context;
    /* mount etdikdə MyContext-in dəyəri ilə kənar effekt edin */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /* MyContext-in dəyəri ilə render edin */
  }
}
MyClass.contextType = MyContext;

contextType klass parametrinə React.createContext() ilə yaranmış Context obyekti təyin edilə bilər. Bu sizə ən yaxında olan kontekstin dəyərini this.context-dən oxumağa icazə verir. Siz bu dəyişəndə olan dəyəri render funksiyası daxil olmaqla bütün lifecycle funksiyalarından istifadə edə bilərsiniz.

Qeyd:

Siz bu API ilə yalnız bir kontekstə abunə ola bilərsiniz. Əgər sizə birdən çox kontekst lazımdırsa Bir Neçə Kontekstin İstehlakı bölməsindən oxuya bilərsiniz.

Əgər siz eksperimental olan public klass sahəsi sintaksisindən istifadə edirsinizsə, siz static klass sahəsindən istifadə edib contextType-ı inisializasiya edə bilərsiniz.

class MyClass extends React.Component {
  static contextType = MyContext;
  render() {
    let value = this.context;
    /* dəyər əsasında render et */
  }
}

Context.Consumer

<MyContext.Consumer>
  {value => /* kontekst dəyəri əsasında render et */}
</MyContext.Consumer>

Kontekst dəyişikliklərinə abunə olan React komponenti. Bu sizə funksional komponent ilə kontekstə abunə olmağa icazə verir.

Funksiyanın uşaq kimi olmasını tələb edir. Bu funksiya, kontekstin cari dəyərini qəbul edir və React nodu qaytarır. Funksiyaya göndərilən value arqumenti, komponent ağacında bu komponentə ən yaxın olan Provider-in value propu ilə eynidir. Əgər provayder yoxdursa, value arqumenti createContext()-ə keçirilən defaultValue propuna bərabər olacaq.

Qeyd

‘funksiyanın uşaq kimi olması’ patterni haqqında daha ətraflı məlumat üçün, render proplar sənədinə baxın.

Context.displayName

Kontekst obyekti displayName mətn parametri qəbul edir. React DevTools bu parametrdən istifadə edərək kontekti hansı adla göstərəcəyini müəyyənləşdirir.

Məsələn, aşağıdakı komponent DevTools-da “MyDisplayName” kimi göstəriləcək:

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

<MyContext.Provider> // DevTools-da "MyDisplayName.Provider" göstəriləcək
<MyContext.Consumer> // DevTools-da "MyDisplayName.Consumer" göstəriləcək

Misallar

Dinamik Kontekst

Şablon üçün dinamik dəyərlər işlədən daha kompleks misal:

theme-context.js

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

export const ThemeContext = React.createContext(
  themes.dark // default dəyər
);

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-dan istifadə edən ara komponent
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-də olan ThemedButton düyməsi
    // state-də olan şablondan istifadə edərkən, xaricindəki komponent
    // default olan dark şablondan istifadə edir
    return (
      <Page>
        <ThemeContext.Provider value={this.state.theme}>
          <Toolbar changeTheme={this.toggleTheme} />
        </ThemeContext.Provider>
        <Section>
          <ThemedButton />
        </Section>
      </Page>
    );
  }
}

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

Konteksti Bir-Birindən Keçən Komponentdən Yeniləmək

Çox vaxt konteksti komponent ağacının dərinliklərində olan bir komponentdən yeniləmək lazım olur. Bu halda, siz kontekstdən dəyərləri Consumer-lərin yeniləməsi üçün funksiya göndərə bilərsiniz:

theme-context.js

// createContext-də işlənən default dəyərin forması
// Consumer-lərin gözlədiyi dəyərdir!
export const ThemeContext = React.createContext({
  theme: themes.dark,
  toggleTheme: () => {},
});

theme-toggler-button.js

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

function ThemeTogglerButton() {
  // Theme Toggler Button theme-dən əlavə
  // toggleTheme funksiyasını da kontekstdən qəbul edir
  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,
      }));
    };

    // Dəyəri yeniləyən funksiyanında state-də olduğundan,
    // bu funksiya da kontekst provayderindən göndəriləcək
    this.state = {
      theme: themes.light,
      toggleTheme: this.toggleTheme,
    };
  }

  render() {
    // Bütün state provayderdən göndəriləcək
    return (
      <ThemeContext.Provider value={this.state}>
        <Content />
      </ThemeContext.Provider>
    );
  }
}

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

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

Bir Neçə Kontekstin İstehlakı

Kontekstin yenidən render edilməsinin tez olması üçün, React hər kontekst consumer-inin komponent ağacında yeni bir nod yaratmalıdır.

// Şablon konteksti, default dəyəri "light"-dır
const ThemeContext = React.createContext('light');

// Login etmiş istifadəçi konteksti
const UserContext = React.createContext({
  name: 'Guest',
});

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

    // App komponenti ilkin kontekst dəyərləri təmin edir
    return (
      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Layout />
        </UserContext.Provider>
      </ThemeContext.Provider>
    );
  }
}

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

// Komponent bir neçə kontekstə abunə ola bilər
function Content() {
  return (
    <ThemeContext.Consumer>
      {theme => (
        <UserContext.Consumer>
          {user => (
            <ProfilePage user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>
  );
}

Əgər 2 və ya daha çox kontekst dəyəri tez-tez birlikdə işlədilirsə, siz bu kontekstlərin dəyərlərini birlikdə təmin edən xüsusi bir render prop komponenti düzəldə bilərsiniz.

Xəbərdarlıq

Konteskt yenidən render etmə zamanını, dəyərin referensi əsasında müəyyənləşdirir. Bu səbəbdən, provider yenidən render etdikdə, consumer-lərdə istənilməyən renderlər ola bilər. Məsələn, aşağıdakı kodda value üçün hər zaman yeni obyekt yarandığından, bütün consumer-lər yenidən render edirlər:

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

Bu problemi həll etmək üçün, dəyəri valideynin state-inə qaldırın:

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

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

Köhnə API

Qeyd

Əvvəl React eksperimental bir kontekst API ilə gəlirdi. Bu köhnə API, React-in bütün 16.x buraxılışlarında dəstəklənəcək. Lakin applikasiyaların yeni API-a miqrasiya edilməyi tövsiyyə edilir. Köhnə API gələcəkdə buraxılan ƏSAS versiyalardan silinəcək. Köhnə kontekst haqqında buradan oxuya bilərsiniz.