الفرق بين المراجعتين لصفحة: «React/lifting state up»
Kinan-mawed (نقاش | مساهمات) لا ملخص تعديل |
Kinan-mawed (نقاش | مساهمات) لا ملخص تعديل |
||
سطر 148: | سطر 148: | ||
// ... | // ... | ||
</syntaxhighlight>نعلم أنّ الخاصيّات <code>props</code> [[React/components and props#.D8.A7.D9.84.D8.AE.D8.A7.D8.B5.D9.8A.D8.A7.D8.AA props .D9.82.D8.A7.D8.A8.D9.84.D8.A9 .D9.84.D9.84.D9.82.D8.B1.D8.A7.D8.A1.D8.A9 .D9.81.D9.82.D8.B7|قابلة للقراءة فقط]]، فعندما كانت درجة الحرارة <code>temperature</code> في الحالة المحليّة لم يكن بإمكان المُكوِّن TemperatureInput إلّا أن يستدعي التابع this.setState() لتغييرها. أمّا الآن وقد أصبحت درجة الحرارة temperature قادمة من الأب كخاصيّة prop فلا يمتلك TemperatureInput أي قدرة على التحكّم بها. | </syntaxhighlight>نعلم أنّ الخاصيّات <code>props</code> [[React/components and props#.D8.A7.D9.84.D8.AE.D8.A7.D8.B5.D9.8A.D8.A7.D8.AA props .D9.82.D8.A7.D8.A8.D9.84.D8.A9 .D9.84.D9.84.D9.82.D8.B1.D8.A7.D8.A1.D8.A9 .D9.81.D9.82.D8.B7|قابلة للقراءة فقط]]، فعندما كانت درجة الحرارة <code>temperature</code> في الحالة المحليّة لم يكن بإمكان المُكوِّن <code>TemperatureInput</code> إلّا أن يستدعي التابع <code>this.setState()</code> لتغييرها. أمّا الآن وقد أصبحت درجة الحرارة <code>temperature</code> قادمة من الأب كخاصيّة <code>prop</code> فلا يمتلك <code>TemperatureInput</code> أي قدرة على التحكّم بها. | ||
تُحلّ هذه المشكلة عادةً في React عن طريق جعل المُكوِّن مضبوطًا (controlled)، فكما يقبل العنصر <input> خاصيّات للقيمة value و | تُحلّ هذه المشكلة عادةً في React عن طريق جعل المُكوِّن مضبوطًا (controlled)، فكما يقبل العنصر <code>[[HTML/input|<input>]]</code> خاصيّات للقيمة <code>value</code> و <code>onChange</code>، فبإمكان العنصر المُخصَّص <code>TemperatureInput</code> أن يقبل خاصيّة لدرجة الحرارة <code>temperature</code> و خاصيّة عند تغيير درجة الحرارة <code>onTemperatureChange</code> من المُكوِّن الأب له وهو <code>Calculator</code>. | ||
عندما يُريد الآن TemperatureInput تحديث درجة حرارته يستدعي this.props.onTemperatureChange: | عندما يُريد الآن <code>TemperatureInput</code> تحديث درجة حرارته يستدعي <code>this.props.onTemperatureChange</code>: |
مراجعة 09:50، 21 يوليو 2018
عادةً ما تحتاج المُكوِّنات المُتعدِّدة إلى أن تعكس نفس البيانات المتغيّرة. نُوصي برفع الحالة المشتركة بينها إلى أقرب عنصر أب مشترك بينها، فلنشاهد كيف يُمكِن تطبيق ذلك عمليًّا.
في هذا القسم سنُنشِئ آلة حاسبة للحرارة والتي تحسب إن كان الماء سيغلي في الدرجة المُعطاة.
سنبدأ بمُكوِّن يُدعى BoilingVerdict
، والذي يقبل درجة الحرارة بالسيلزيوس celsius
كخاصيّة props
له، ويطبع إن كانت هذه الدرجة كافية لغلي الماء:
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>سيغلي الماء.</p>;
}
return <p>لن يغلي الماء.</p>;
}
سنُنشِئ الآن مُكوِّن الآلة الحاسبة Calculator
، والذي يُصيِّر حقل إدخال <input>
يُتيح لنا إدخال درجة الحرارة ويحتفظ بقيمتها في this.state.temperature
. يُصيِّر هذا المُكوِّن أيضًا المُكوِّن BoilingVerdict
مع تزويده بدرجة الحرارة التي أدخلها المستخدم:
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
}
handleChange(e) {
this.setState({temperature: e.target.value});
}
render() {
const temperature = this.state.temperature;
return (
<fieldset>
<legend>أدخل درجة الحرارة بالسيلزيوس:</legend>
<input
value={temperature}
onChange={this.handleChange} />
<BoilingVerdict
celsius={parseFloat(temperature)} />
</fieldset>
);
}
}
إضافة حقل إدخال آخر
المتطلّب الآخر الذي نريده إلى جانب إدخال الحرارة بالسيلزيوس هو تزويد المستخدم بحقل إدخال لدرجة الحرارة بالفهرنهايت وإبقائهما متزامنين معًا. بإمكاننا البدء باستخراج المُكوِّن TemperatureInput
من المُكوِّن Calculator
. سنضيف خاصيّة جديدة وهي المقياس scale
والتي يُمكِن أن تكون قيمتها "c"
أو "f"
:
const scaleNames = {
c: 'سيلزيوس',
f: 'فهرنهايت'
};
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
}
handleChange(e) {
this.setState({temperature: e.target.value});
}
render() {
const temperature = this.state.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
نستطيع الآن تغيير المُكوِّن Calculator
لتصيير حقلين منفصلين لإدخال درجة الحرارة:
class Calculator extends React.Component {
render() {
return (
<div>
<TemperatureInput scale="c" />
<TemperatureInput scale="f" />
</div>
);
}
}
نمتلك الآن حقلي إدخال، ولكن إن أدخلت درجة الحرارة في أحدهما فلن تتحدّث قيمة الآخر، وهذا يتعارض مع متطلّباتنا بأن نجعل القيمتين متزامنتان.
لا يُمكننا أيضًا عرض المُكوِّن BoilingVerdict
من خلال المُكوِّن Calculator
، حيث لا يعرف هذا الأخير درجة الحرارة الحاليّة لأنّها مخفيّة بداخل المُكوِّن TemperatureInput
.
كتابة دوال التحويل
سنكتب أولًا دالتين للتحويل من سيلزيوس إلى فهرنهايت وبالعكس:
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
تُحوِّل هاتان الدالتان الأرقام، سنكتب دالة أخرى تقبل سلسلة نصيّة لدرجة الحرارة temperature
ودالة للتحويل تستقبل وسائط وتُعيد سلسلة نصيّة. سنستخدمها لحساب قيمة أحد حقول الإدخال بناءً على قيمة الآخر.
تُعيد هذه الدالة سلسلة نصيّة فارغة عند إدخال درجة حرارة خاطئة، وتُقرِّب الناتج إلى ثلاث قيم بعد الفاصلة:
function tryConvert(temperature, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
على سبيل المثال استخدام هذه الدالة بالشكل tryConvert('abc', toCelsius)
يُعيد سلسلة نصيّة فارغة، واستخدامها بالشكل tryConvert('10.22', toFahrenheit)
يُعيد القيمة '50.396'
.
رفع الحالة للأعلى
يحتفظ إلى حدّ الآن كل من المُكوِّنين TemperatureInput
بقيمهما بشكلٍ مُستقلٍ في الحالة المحليّة:
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
}
handleChange(e) {
this.setState({temperature: e.target.value});
}
render() {
const temperature = this.state.temperature;
// ...
ولكن نريد مزامنة هذين الحقلين مع بعضهما، فعندما نُحدِّث حقل إدخال درجة الحرارة بالسيلزيوس فيجب على حقل درجة الحرارة بالفهرنهايت أن يعكس قيمة درجة الحرارة المُحوَّلة، وبالعكس.
تُنفَّذ مشاركة الحالة في React عن طريق نقلها إلى أقرب مُكوِّن مشترك للمُكوِّنات التي تحتاجها، ويُدعى هذا برفع الحالة للأعلى (lifting state up). سنُزيل الحالة المحليّة من TemperatureInput
وننقلها إلى Calculator
بدلًا من ذلك.
إن كان المُكوِّن Calculator
يمتلك الحالة المشتركة، فسيُصبِح "مصدر الحقيقة" بالنسبة لدرجة الحرارة الحاليّة في حقلي الإدخال، حيث يُمكِنه توجيه الأوامر لهما بأن يمتلكا قيم متوافقة مع بعضهما. بما أنّ الخاصيّات props
في المكونين TemperatureInput
قادمة من نفس الأب وهو المُكوِّن Calculator
، فسيبقى الحقلان متزامنان دومًا.
فلنشاهد كيفية عمل ذلك خطوةً بخطوة.
بدايةً سنضع this.props.temperature
بدلًا من this.state.temperature
في المُكوِّن TemperatureInput
. فلنفترض الآن وجود this.props.temperature
على الرغم من أنّنا سنحتاج إلى تمريره من المُكوِّن Calculator
:
render() {
// سابقًا كنا نضع: const temperature = this.state.temperature;
const temperature = this.props.temperature;
// ...
نعلم أنّ الخاصيّات props
قابلة للقراءة فقط، فعندما كانت درجة الحرارة temperature
في الحالة المحليّة لم يكن بإمكان المُكوِّن TemperatureInput
إلّا أن يستدعي التابع this.setState()
لتغييرها. أمّا الآن وقد أصبحت درجة الحرارة temperature
قادمة من الأب كخاصيّة prop
فلا يمتلك TemperatureInput
أي قدرة على التحكّم بها.
تُحلّ هذه المشكلة عادةً في React عن طريق جعل المُكوِّن مضبوطًا (controlled)، فكما يقبل العنصر <input>
خاصيّات للقيمة value
و onChange
، فبإمكان العنصر المُخصَّص TemperatureInput
أن يقبل خاصيّة لدرجة الحرارة temperature
و خاصيّة عند تغيير درجة الحرارة onTemperatureChange
من المُكوِّن الأب له وهو Calculator
.
عندما يُريد الآن TemperatureInput
تحديث درجة حرارته يستدعي this.props.onTemperatureChange
: