Context
Context راهی را برای انتقال داده در درخت کامپوننت بدون نیاز به انتقال دستیه props به تمام سطح های پایینی فراهم میکند.
در یک اپلیکیشن معمولی ریاکت, داده از طریق props از بالا به پایین (والدین به فرزند) منتقل میشود, اما این کار برای انواع خاصی از props ها (برای مثال: locale preference, تم رابط کاربری) که مورد نیاز بسیاری از کامپوننت ها در یک اپلیکیشن است میتواند سنگین باشد. Context راهی را برای به اشتراک گذاری مقادیری مانند این بین کامپوننتها بدون نیاز به انتقال prop صریحاً از هر سطح درخت فراهم میکند.
چه موقع باید از Context استفاده کرد
Context برای به اشتراک گذاری داده ای طراحی شده است که میتواند برای درختی از ریاکت کامپوننت ها عمومی تلقی شود٬ مانند کاربر تایید شدهی فعلی٬ زمینه٬ یا زبان ترجیحی. برای مثال٬ ما در کد زیر یک prop به نام “theme” را به صورت دستی برای style دادن به کامپوننت Button انتقال میدهیم:
class App extends React.Component {
render() {
return <Toolbar theme="dark" />;
}
}
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} /> </div>
);
}
class ThemedButton extends React.Component {
render() {
return <Button theme={this.props.theme} />;
}
}
با استفاده از 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 />
</ThemeContext.Provider>
);
}
}
// A component in the middle doesn't have to// pass the theme down explicitly anymore.function Toolbar() {
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 زمانی استفاده میشود که برخی از دادهها باید توسط بسیاری از کامپوننتها در سطحهای مختلف تودرتویی در دسترس قرار بگیرند. از آنجایی که استفاده مجدد کامپوننت را سختتر میکند٬ از آن کم استفاده کنید.
اگر فقط میخواهید که از انتقال برخی از دادهها بین بسیاری از سطحها اجتناب کنید, اغلب composition کامپوننت راهحلی سادهتر از context میباشد.
برای مثال٬ یک کامپوننت به نام Page
را در نظر بگیرید که یک prop به نام user
و avatarSize
را چندین سطح به پایین منتقل میکند به طوری که کامپوننتهای عمیقا تودرتو شدهی Link
و Avatar
بتوانند آن را بخوانند:
<Page user={user} avatarSize={avatarSize} />
// ... که رندر میکند ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... که رندر میکند ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... که رندر میکند ...
<Link href={user.permalink}>
<Avatar user={user} size={avatarSize} />
</Link>
انتقال propهای user
و avatarSize
به پایین از بین بسیاری از سطحها ممکن است زائد به نظر بیاید اگر در نهایت فقط کامپوننت Avatar
است که به آن نیاز دارد. این نیز آزاردهنده است که هر زمانی که کامپوننت Avatar
به تعداد بیشتری props از بالا احتیاج داشته باشد٬ شما باید آنها را در تمامی سطحهای میانی هم اضافه کنید.
یک راه حل برای این مسئله بدون context این است که خود کامپوننت Avatar
را به پایین انتقال دهیم به طوری که کامپوننتهای میانی نیازی به دانستن درمورد propهای user
یا avatar
نداشته باشند:
function Page(props) {
const user = props.user;
const userLink = (
<Link href={user.permalink}>
<Avatar user={user} size={props.avatarSize} />
</Link>
);
return <PageLayout userLink={userLink} />;
}
// اکنون, داریم:
<Page user={user} avatarSize={avatarSize} />
// ... که رندر میکند ...
<PageLayout userLink={...} />
// ... که رندر میکند ...
<NavigationBar userLink={...} />
// ... که رندر میکند ...
{props.userLink}
با این تغییر٬ فقط بالاترین کامپوننت یعنی Page باید در مورد استفاده user
و avatarsize
توسط کامپوننتهای Link
و Avatar
بداند.
این وارونگی کنترل در بسیاری از موارد میتواند کد شما را از طریق کاهش تعداد propهایی که باید در اپلیکیشن خود انتقال دهید تمیزتر کند و کنترل بیشتری به کامپوننتهای پایه میدهد. با این حال٬ این کار در هر موردی تصمیم درست نیست: انتقال پیچیدگی بیشتر به بالا در درخت باعث پیچیدهتر شدن کامپوننتهای سطح-بالا میشود و ممکن است کامپوننتهای سطح-پایین را مجبور به انعطافپذیری بیشتر از آنچه که میخواهید کند.
شما برای یک کامپوننت محدود به یک فرزند نیستید. شما میتوانید چندین فرزند٬ یا حتی چندین “slots” جداگانه برای فرزندان٬ همانطور که در اینجا مستند شده است انتقال دهید:
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}
/>
);
}
این الگو در بسیاری از مواردی که باید یک فرزند را از والدین نزدیک آن جدا کرد کافی است. در صورتی که فرزند قبل از رندر شدن نیاز به برقراری ارتباط با والدین را داشته باشد٬ شما میتوانید این قضیه را با render-props ادامه دهید.
با این حال٬ گاهیاوقات یک دادهی یکسان باید توسط کامپوننتهای زیادی در درخت٬ و سطحهای تودرتوی مختلفی در دسترس قرار گیرد. Context اجازه پخش کردن همچین دادهای٬ و اجازه تغییر آن به تمام کامپوننتهای زیرین را به شما میدهد. مثالهای رایجی که استفاده از context در آنها ممکن است آسانتر از راههای جایگزین آن باشد شامل مدیریت locale فعلی٬ زمینه, یا داده حافظه نهان میباشد.
API
React.createContext
const MyContext = React.createContext(defaultValue);
کد بالا یک شیٔ context ایجاد میکند. وقتی ریاکت یک کامپوننتی را رندر میکند که به این شیء context ارجاع میکند (subscribes)٬ مقدار context حاضر را از نزدیکترین Provider
مرتبط بالایی در درخت خواهد خواند.
آرگومان defaultValue
فقط زمانی استفاده میشود که یک کامپوننت در بالاتر از خود در درخت یک Provider مطابق نداشته باشد. این مورد میتواند برای تست کردن کامپوننتها در انزوا بدون wrap کردن آنها مفید باشد. توجه داشته باشید: انتقال undefined
به عنوان مقدار Provider باعث نمیشود که کامپوننتهایی که از آن استفاده میکنند از defaultValue
استفاده کنند.
Context.Provider
<MyContext.Provider value={/* یک مقداری */}>
هر شئ context ای با یک کامپوننت ریاکتی Provider همراه میشود که به کامپوننتهای مصرفکننده آن این اجازه را میدهد که تغییرات context را به اشتراک بگذارند.
کامپوننت Provider یک prop به نام value
را میپذیرد که به کامپوننتهای مصرفکننده آن که نوادگان این Provider میباشند٬ انتقال یابد. Provider میتواند به چندین مصرفکننده متصل شود. Providerها میتوانند به شکلی تودرتو شوند تا valueهایی را که در عمق درخت وجود دارند باطل کنند..
هرزمانی که prop value
مربوط به Provider تغییر کند تمام مصرفکنندههایی که نوادگان یک Provider هستند دوباره رندر میشوند. Propagation از Provider تا نوادگان مصرفکننده آن (شامل .contextType
و useContext
) مشمول متد shouldComponentUpdate
نیستند٬ بنابراین حتی زمانی که جد کامپوننت یک به روزرسانی را رد میکند٬ مصرفکننده آن به روزرسانی میشود.
تغییرات بوسیله مقایسه value های جدید و قدیم با استفاده از همان الگوریتم Object.is
مشخص میشوند.
یادداشت
نحوه تعیین تغییرات هنگام انتقال اشیاء به عنوان
value
میتواند مشکلساز شود: هشدارها را ببینید.
Class.contextType
class MyClass extends React.Component {
componentDidMount() {
let value = this.context;
/* با استفاده از مقدار MyContext یک اثرجانبی هنگام mount ایجاد کنید */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* چیزی را بر اساس مقدار MyContext رندر کنید */
}
}
MyClass.contextType = MyContext;
یک شیئ Context که با React.createContext()
ایجاد شده است میتواند به ویژگی contextType
در یک کلاس اختصاص یابد. این کار به شما اجازه میدهد که از نزدیکترین مقدار فعلی contextType
با استفاده از this.context
استفاده کنید. شما میتوانید از این قضیه در تمام متدهای چرخهحیات از جمله تابع رندر استفاده کنید.
یادداشت:
شما با استفاده از این API فقط می توانید در یک context واحد مشترک شوید. در صورت نیاز به خواندن بیش از یک مورد ، به مصرف چندین Context مراجعه کنید .
اگر از syntax تجربی فیلدهای کلاس عمومی استفاده می کنید٬ می توانید از یک فیلد کلاس استاتیک برای مقداردهی اولیه
contextType
خود استفاده کنید.
class MyClass extends React.Component {
static contextType = MyContext;
render() {
let value = this.context;
/* چیزی را بر اساس مقدار آن رندر کنید */
}
}
Context.Consumer
<MyContext.Consumer>
{value => /* چیزی را بر اساس مقدار context رندر کنید */}
</MyContext.Consumer>
یک کامپوننت ریاکت که تغییرات context را به اشتراک میگذارد. این کار به شما اجازه میدهد که یک context را داخل کامپوننت تابع به اشتراک بگذارید.
نیاز به یک کامپوننت به عنوان فرزند دارد. این تابع مقدار context فعلی را دریافت میکند و یک نود ریاکت برمیگرداند. آرگومان value
ای که به تابع داده شده است با prop value
نزدیکترین Provider بالاتر از آن در درخت برای این context برابر خواهد بود. اگر در بالاتر هیچ Provider ای برای این context وجود نداشت٬ آرگومان value
با defaultValue
ای که به createContext()
داده شد٬ برابر خواهد بود.
یادداشت
برای اطلاعات بیشتر در مورد الگوی ‘تابع به عنوان فرزند’٬ render props را ببینید.
Context.displayName
شیئ context یک ویژگی از نوع رشته با نام displayName
میپذیرد. DevTools ریاکت از این رشته استفاده میکند تا مشخص کند که چه چیزی را برای عنوان context نمایش دهد.
به عنوان مثال٬ کامپوننت زیر در DevTools به صورت MyDisplayName ظاهر میشود:
const MyContext = React.createContext(/* یک مقداری */);
MyContext.displayName = 'MyDisplayName';
<MyContext.Provider> // "MyDisplayName.Provider" in DevTools
<MyContext.Consumer> // "MyDisplayName.Consumer" in DevTools
مثال ها
Context پویا
یک مثال پیچیدهتر با مقادیر پویا برای theme:
theme-context.js
export const themes = {
light: {
foreground: '#000000',
background: '#eeeeee',
},
dark: {
foreground: '#ffffff',
background: '#222222',
},
};
export const ThemeContext = React.createContext( themes.dark // default value);
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';
// An intermediate component that uses the 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() {
// The ThemedButton button inside the ThemeProvider // uses the theme from state while the one outside uses // the default dark theme return (
<Page>
<ThemeContext.Provider value={this.state.theme}> <Toolbar changeTheme={this.toggleTheme} /> </ThemeContext.Provider> <Section>
<ThemedButton /> </Section>
</Page>
);
}
}
ReactDOM.render(<App />, document.root);
بهروزرسانی Context از یک کامپوننت تودرتو
اغلب به روزرسانی context از کامپوننتای که در جایی عمیق در درخت واقع شده لازم است. در این مورد شما میتوانید یک تابع را از طریق context به پایین منتقل کنید تا به مصرفکنندگان آن اجازه به روزرسانی context را بدهید:
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);
مصرف چندین context
برای اینکه رندرشدن های مجدد را سریع نگه داریم٬ ریاکت باید هر مصرفکننده context را به یک نود جداگانه در درخت تبدیل کند.
// Theme context, default to light theme
const ThemeContext = React.createContext('light');
// Signed-in user context
const 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ای خودتان ایجاد کنید که شامل هر دوی آنها شود.
هشدارها
به دلیل اینکه context از هویت مرجع برای مشخص کردن اینکه چه زمانی باید رندر دوباره صورت بگیرد استفاده میکند٬ به دلیل برخی خطاها ممکن است هنگامی که والدین Provider دوباره رندر میشوند٬ در مصرفکنندگان آن موجب رندر ناخواسته شود. برای مثال٬ هر زمانی که Provider دوباره به خاطر ایجاد یک شیئ جدید که همیشه برای value
ایجاد میشود٬ رندر میشود کد زیر تمام مصرفکنندگان را دوباره رندر میکند:
class App extends React.Component {
render() {
return (
<MyContext.Provider value={{something: 'something'}}> <Toolbar />
</MyContext.Provider>
);
}
}
برای پی بردن به این موضوع٬ مقدار را به state والدین ببرید:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: {something: 'something'}, };
}
render() {
return (
<Provider value={this.state.value}> <Toolbar />
</Provider>
);
}
}
API موروثی
یادداشت
ریاکت قبلا با یک API context آزمایشی منتشر شد. API قدیمی در تمام نسخه های ۱۶.x پشتیبانی خواهد شد٬ اما اپلیکیشنهایی که از آن استفاده میکنند باید به نسخه جدید ارتقاء دهند. API موروثی در نسخه اصلی React در آینده حذف می شود. اسناد context موروثی را اینجا بخوانید.