استخدام السياق (Context)
These docs are old and won’t be updated. Go to react.dev for the new React docs.
These new documentation pages teach modern React and include live examples:
يُزوِّدنا السياق (Context) بطريقة لتمرير البيانات عبر شجرة المُكوّنات دون الحاجة لتمرير الخاصيّات props
يدويًّا من الأعلى إلى الأسفل في كل مستوى.
تُمرَّر البيانات في تطبيقات React الإعتيادية من المستوى الأعلى إلى الأسفل (أي من المكوّنات الآباء إلى المكوّنات الأبناء) عبر الخاصيّات props
، ولكن قد يكون هذا بطيئًا لبعض أنواع الخاصيّات (مثل تفضيلات اللغة وقوالب واجهة المستخدم) والتي تحتاجها العديد من المكوّنات ضمن التطبيق. يُزوِّدنا السياق بطريقة لمشاركة القيم مثل تلك الموجودة بين المكوّنات دون الاضطرار لتمرير الخاصيّات عبر كل مستوى من الشجرة.
متى نستخدم السياق
يكون السياق مُصمَّمًا لمشاركة البيانات التي تُعتبر عامّة (global) لشجرة مكوّنات React، مثل المستخدم قيد المصادقة حاليًّا، أو القالب، أو تفضيلات اللغة. على سبيل المثال في الشيفرة التالية نُمرِّر خاصيّة القالب ”theme
” يدويًّا من أجل تنسيق مكوّن الزر 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 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} />; }
}
قبل أن تستخدم السياق
يُستخدَم السياق بشكل أساسي عند الحاجة للوصول إلى بعض البيانات من قبل العديد من المكونات في مستويات متداخلة مختلفة، ولكن لا يجب استخدامه بكثرة لأنّه يجعل من إعادة استخدام المكونات أمرًا أكثر صعوبة.
إن أردت فقط تجنّب تمرير بعض الخاصيّات عبر العديد من المستويات, فسيكون استخدام التراكيب حلًّا أسهل من استخدام السياق.
على سبيل المثال افترض وجود مكون للصفحة Page
والذي يُمرِّر خاصيّات المستخدم user
وحجم الصورة الرمزية avatarSize
عبر مستويات عديدة إلى الأسفل بحيث تتمكّن مكونات الرابط Link
والصورة الرمزية Avatar
الموجودة في مستويات عميقة ومتداخلة أن تقرأها:
<Page user={user} avatarSize={avatarSize} />
// ... which renders ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... which renders ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... which renders ...
<Link href={user.permalink}>
<Avatar user={user} size={avatarSize} />
</Link>
قد يبدو من الفائض تمرير الخاصيّات user
و avatarSize
عبر مستويات عديدة إلى الأسفل إن كان يحتاجه في النهاية فقط المكوّن Avatar
. من المزعج أيضًا كلّما احتاج المُكوِّن Avatar
المزيد من الخاصيّات من المستويات الأعلى فيجب عليك إضافتها في كل المستويات الوسيطة أيضًا.
من طرق حل هذه المشكلة دون السياق تمرير المكون Avatar
نفسه للأسفل بحيث لا تحتاج المكوّنات الوسيطة أن تعلم حول الخاصيّة user
أو 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} />;
}
// Now, we have:
<Page user={user} avatarSize={avatarSize} />
// ... which renders ...
<PageLayout userLink={...} />
// ... which renders ...
<NavigationBar userLink={...} />
// ... which renders ...
{props.userLink}
مع هذا التغيير يحتاج فقط المكون Page
ذو المستوى الأعلى إلى أن يعرف عن استخدام المكوّنات Link
و Avatar
للمكوّنات user
و avatarSize
.
يؤدي قلب السيطرة هذا إلى جعل شيفرتك أسهل في العديد من الحالات عن طريق تقليل كمية الخاصيّات التي تحتاج تمريرها عبر تطبيقك ويُعطيك سيطرة أكبر على المكوّنات الجذريّة. على الرغم من ذلك، لا يكون ذلك هو الخيار الأنسب في كل حالة، حيث أنّ نَقل المزيد من التعقيد إلى مستوى أعلى في الشجرة يجعل من المكونات ذات المستوى الأعلى أكثر تعقيدًا ويجبر المكونات ذات المستويات الأدنى أن تكون مرنة أكثر مما قد ترغب.
لن تكون محدودًا بمكوّن ابن واحد، فبإمكانك تمرير مكونات أبناء متعددة أو حتى امتلاك منافذ منفصلة متعددة للأبناء كما هو موثق هنا:
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}
/>
);
}
يكفينا هذا النمط للعديد من الحالات عند الحاجة لفصل مكوّن ابن عن المكونات الآباء له. وبإمكانك أخذ هذا إلى أبعد من ذلك عن طريق خاصيّات التصيير إن كان المكوّن الابن يحتاج إلى التواصل مع المكوّن الأب قبل التصيير.
على أية حال، تحتاج بعض البيانات أحيانًا أن تكون قابلة للوصول من قبل العديد من المكوّنات في الشجرة، وبمستويات متداخلة مختلفة. يُتيح لك السياق نشر مثل هذه البيانات وتغييراتها إلى جميع المكوّنات في المستويات الأدنى. تتضمّن الأمثلة الشائعة التي يكون فيها استخدام السياق أسهل من البدائل: إدارة اللغة الحالية، أو القالب، أو مخبأ البيانات (cache).
API
React.createContext
const MyContext = React.createContext(defaultValue);
يُنشِئ الزوج { Provider, Consumer }
.
عند تصيير React للسياق Consumer
فستقرأ قيمة السياق الحالية من أقرب مُزوِّد Provider
فوقها في الشجرة.
يُستخدَم الوسيط defaultValue عن طريق المستهلك Consumer
فقط عندما لا يجد مزوّد Provider
مُطابِق فوقه في الشجرة. يُفيد هذا من أجل اختبار المُكوّنات على انفراد بدون تغليفها.
لا يُؤدّي تمرير القيمة undefined
كقيمة للمُزوِّد إلى استخدام المستهلك Consumer
للقيمة defaultValue
.
Context.Provider
<MyContext.Provider value={/* some value */}>
وهو مُكوِّن React الذي يسمح للمستهلك Consumer بأن يُشارك في تغييرات السياق.
تقبل الخاصية value
بأن تعطى للمكونات المتستهلكة consuming components
التي تنحدر من مزود. من الممكن لمزود واحد فقط أن يربط مع عدة مستهلكات. يمكن تداخل المزودات لتجاوز القيم بشكل أعمق داخل الشجرة.
كل المستهلكات Consumers
المنحدرة عن المُزوِّد ستُعيد التصيير عندما تتغير قيمة الخاصيّة value
للمُزوّد. لا يخضع الانتشار من المُزوّد إلى المستهلكات المنحدرة عنه (بما فيها .contextType
وuseContext
) إلى التابع shouldComponentUpdate
، لذا يُحدَّث المستهلك حتى ولو كان المكوّن الأب غير خاضع للتحديث.
تُحدَّد التغييرات عن طريق مقارنة القيم الجديدة والقديمة باستخدام نفس الخوارزمية مثل Object.is
.
ملاحظة
قد تُسبِّب طريقة التغييرات المُحدَّدة بعض المشاكل عند تمرير الكائنات في الوسيط
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;
يمكن إسناد الخاصية contextType
p في أي صنف إلى كائن سياق (Context object) أنشئ بوساطة React.createContext()
. يمكِّنك ذلك من استهلاك أقرب قيمة حالية لنوع ذلك السياق باستعمال this.context
. تستطيع الإشارة عبر مرجع إلى هذا في أي تابع من توابع دورة الحياة بما فيها الدالة render
.
ملاحظة:
تستطيع الاشتراك في سياق وحيد باستعمال الواجهة البرمجية هذه. إن احتجت إلى قراءة المزيد من أكثر من سياق واحد، اطلع على القسم استهلاك سياقات متعددة.
إن كنت تستخدم صياغة حقول الصنف العامة public class fields syntax التجريبية، تستطيع استعمال حقل صنف ساكن (static class field) لتهيئة نوع السياق
contextType
الخاص بك.
class MyClass extends React.Component {
static contextType = MyContext;
render() {
let value = this.context;
/* value صيّر شيئًا بناءً على القيمة */
}
}
Context.Consumer
<MyContext.Consumer>
{value => /* صيّر شيئًا بناء على قيمة السياق */}
</MyContext.Consumer>
يتغير مكون React الذي يشترك بسياق، وهذا يمكِّنك من الاشتراك بسياق ضمن مكون دالة.
تتطلب الخاصية Consumer
دالةً على أنَّها ابنٌ. إذ تستقبل هذه الدالة قيمة السياق الحالي وتعيد عقدة React. الوسيط value
المُمرَّر إلى الدالة سيكون مساويًّا إلى قيمة الخاصية value
لأقرب مزود (Provider) لهذا السياق في الشجرة أعلاه. إن لم يكن هنالك مزود (Provider) لهذا السياق أعلاه، فسيكون الوسيط value
مساويًا إلى القيمة defaultValue
التي مُرِّرت إلى ()createContext
.
ملاحظة
للمزيد من المعلومات حول النمط “دالة على أنَّها ابنٌ”، اطلع على توثيق خاصيات التصيير.
Context.displayName
كائن السياق يقبل خاصية displayName
string. React DevTools يستخدم هذه السلسلة لتحديد ما يجب عرضه للسياق.
مثلا، المكون التالي سيظهر بصفته MyDisplayName على الـDevTools:
const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';
<MyContext.Provider> // "MyDisplayName.Provider" in DevTools
<MyContext.Consumer> // "MyDisplayName.Consumer" in DevTools
أمثلة
السياق الديناميكي
مثال أكثر تعقيدًا مع قيم ديناميكية للقالب :
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>
);
}
}
const root = ReactDOM.createRoot(
document.getElementById('root')
);
root.render(<App />);
تحديث السياق من المكونات المتداخلة
من الضروري أحيانًا تحديث السياق من المكون المتداخل بعمق في مكانٍ ما من شجرة المكونات. في هذه الحالة تستطيع تمرير دالة إلى الأسفل عبر السياق للسماح للمستهلكات بتحديث السياق:
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>
);
}
const root = ReactDOM.createRoot(
document.getElementById('root')
);
root.render(<App />);
استهلاك سياقات متعددة
لإبقاء قدرة السياق على إعادة التصيير بشكل سريع، تحتاج React إلى جعل كل مستهلك سياق على شكل عقدة منفصل في الشجرة:
// 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> );
}
إن كانت قيمة سياقين أو أكثر مستخدمة معًا، فقد ترغب بالنظر إلى إنشاء مكون خاصية التصيير الخاص بك والذي يزودك بكليهما معًا.
محاذير
بما أنّ السياق يستخدم هوية المرجع لتحديد وقت إعادة التصيير، فهنالك بعض الأشياء التي قد تُطلِق تصييرات غير مقصودة في المستهلكات consumers
عندما يُعيد المُزوِّد provider
الأب التصيير. على سبيل المثال ستُعيد الشيفرة التالية تصيير جميع المستهلكات في كل مرة يُعيد فيها المُزوّد التصيير، وذلك بسبب إنشاء كائن جديد دومًا للخاصيّة value
:
class App extends React.Component {
render() {
return (
<MyContext.Provider value={{something: 'something'}}> <Toolbar />
</MyContext.Provider>
);
}
}
ولكي تلتف على هذا احتفظ بقيمة value
في حالة الأب:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: {something: 'something'}, };
}
render() {
return (
<MyContext.Provider value={this.state.value}> <Toolbar />
</MyContext.Provider>
);
}
}
واجهة برمجة التطبيقات القديمة
ملاحظة
كانت تأتي React سابقًا مع واجهة برمجة تطبيقات (API) تجريبية للسياق. ستبقى هذه الواجهة مدعومة في جميع الإصدارات 16.x، ولكن يجب على التطبيقات التي تستخدمها أن تنتقل إلى الإصدار الجديد. ستُزال هذه الواجهة القديمة في إصدار React المستقبلي الرئيسي. اقرأ توثيق السياق القديم من هنا.