الفرق بين المراجعتين ل"ReactNative/direct manipulation"

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
سطر 167: سطر 167:
 
مشابهة للدالة ‎‎<code>measure()</code>‎‎، ولكنّها تقيس العرض نسبةً إلى مكوّنٍ جدٍّ (ancestor)، المحدَّد بالاسم ‎‎<code>relativeToNativeNode</code>‎‎. هذا يعني أن القيمتان ‎‎<code>x</code>‎‎ و‎‎<code>y</code>‎‎ نسبيّتان نسبةً إلى القيمتين الأصليتين ‎‎<code>x</code>‎‎ و‎‎<code>y</code>‎‎ الخاصّتين بالعرض الجد (ancestor view).
 
مشابهة للدالة ‎‎<code>measure()</code>‎‎، ولكنّها تقيس العرض نسبةً إلى مكوّنٍ جدٍّ (ancestor)، المحدَّد بالاسم ‎‎<code>relativeToNativeNode</code>‎‎. هذا يعني أن القيمتان ‎‎<code>x</code>‎‎ و‎‎<code>y</code>‎‎ نسبيّتان نسبةً إلى القيمتين الأصليتين ‎‎<code>x</code>‎‎ و‎‎<code>y</code>‎‎ الخاصّتين بالعرض الجد (ancestor view).
  
ملاحظة: يمكن استدعاء هذه الدالة مع معالج relativeToNativeNode أيضًا (بدلًا من المرجع)، لكن يهمل هذا الاختلاف.
+
ملاحظة: يمكن استدعاء هذه الدالة مع معالج <code>relativeToNativeNode</code> أيضًا (بدلًا من المرجع)، لكن يهمل هذا الاختلاف.
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
import {findNodeHandle} from 'react-native';
+
import React, { useEffect, useRef, useState } from "react";
 +
import { Text, View, StyleSheet } from "react-native";
 +
 
 +
const App = () => {
 +
  const textContainerRef = useRef(null);
 +
  const textRef = useRef(null);
 +
  const [measure, setMeasure] = useState(null);
 +
 
 +
  useEffect(() => {
 +
    if (textRef.current && textContainerRef.current) {
 +
      textRef.current.measureLayout(
 +
        textContainerRef.current,
 +
        (left, top, width, height) => {
 +
          setMeasure({ left, top, width, height });
 +
        }
 +
      );
 +
    }
 +
  }, [measure]);
 +
 
 +
  return (
 +
    <View style={styles.container}>
 +
      <View
 +
        ref={textContainerRef}
 +
        style={styles.textContainer}
 +
      >
 +
        <Text ref={textRef}>
 +
          Where am I? (relative to the text container)
 +
        </Text>
 +
      </View>
 +
      <Text style={styles.measure}>
 +
        {JSON.stringify(measure)}
 +
      </Text>
 +
    </View>
 +
  );
 +
};
 +
 
 +
const styles = StyleSheet.create({
 +
  container: {
 +
    flex: 1,
 +
    justifyContent: "center",
 +
  },
 +
  textContainer: {
 +
    backgroundColor: "#61dafb",
 +
    justifyContent: "center",
 +
    alignItems: "center",
 +
    padding: 12,
 +
  },
 +
  measure: {
 +
    textAlign: "center",
 +
    padding: 12,
 +
  },
 +
});
 +
 
 +
export default App;
 
</syntaxhighlight>
 
</syntaxhighlight>
 
===‎‎<code>focus()</code>‎‎===
 
===‎‎<code>focus()</code>‎‎===

مراجعة 17:01، 2 أبريل 2021


المعالجة المباشرة

من الضروري في بعض الأحيان إجراء تغييرات مباشرة على مكوّن دون استخدام الحالة أو الخاصيات لإطلاق إعادة تصييرٍ (re-render) لكامل الشجرة الفرعية (subtree). عند استخدام React في المتصفح على سبيل المثال، تحتاج أحيانًا إلى تعديل عقدة DOM مباشرةً، وينطبق الأمر نفسه على العروض في تطبيقات الجوال. setNativeProps هي المكافئُ في React Native لضبط الخاصيات مباشرة على عقدة DOM.

ملاحظة: استخدم setNativeProps عندما يؤدي إعادة التصيير المتكرر إلى إعاقةٍ في الأداء.

لا يجب أن تستخدم المعالجة المباشرة كثيرًا. عادةً ما ستستخدمها فقط لإنشاء تحريكاتٍ مستمرة لتجنّب الحمل الزائد (overhead) في تصيير التسلسل الهرمي للمكونات (component hierarchy) والمطابقة (Reconciliation) بين العديد من العروض. تُعدّ الدالة setNativeProps دالّةً أمريّةً (imperative) وتُخزِّن الحالة في الطبقة الأصيلة (DOM، و UIView، إلخ) وليس في مكونات React الخاصة بك، مما يجعل شرح الشيفرة الخاصّة بك وفهمها أكثر صعوبة وتعقيدًا. قبل استخدامها، حاول حل مشكلتك باستخدام setState و shouldComponentUpdate.

setNativeProps مع مكون TouchableOpacity

يستخدم مكوّن TouchableOpacity دالّة setNativeProps داخليًا لتحديث عتامةِ (opacity) مكوّناته الأبناء:

const viewRef = useRef();
const setOpacityTo = useCallback((value) => {
  // Redacted: animation related code
  viewRef.current.setNativeProps({
    opacity: value
  });
}, []);

هذا يسمح لنا بكتابة الشيفرة البرمجية التالية ومعرفة أنّ عتامة المكون الابن سوف تُحدَّثُ استجابةً للّمس، دون أن يكون لدى المكون الابن أي معرفة بهذه الحقيقة أو يتطلب أي تغييرات في شيفرته:

<TouchableOpacity onPress={handlePress}>
  <View>
    <Text>Press me!</Text>
  </View>
</TouchableOpacity>

لنتخّيّل أنّ ميّزة setNativeProps غير موجودة في إطار العمل. إحدى الطرق التي قد نستخدمها للتخلّص من هذا القيد هي تخزين قيمة العتامة في الحالة، ثم تحديث تلك القيمة في كلّ مرّة تُطلق فيها الدالة onPress:

const [buttonOpacity, setButtonOpacity] = useState(1);
return (
  <TouchableOpacity
    onPressIn={() => setButtonOpacity(0.5)}
    onPressOut={() => setButtonOpacity(1)}>
    <View style={{ opacity: buttonOpacity }}>
      <Text>Press me!</Text>
    </View>
  </TouchableOpacity>
);

يتطلّب هذا حسابًا كثيفًا مقارنة بالمثال الأصلي، إذ يحتاج React إلى إعادة تصيير التسلسل المكونات الهرمي في كل مرة تتغير فيها العتامة، على الرغم من عدم تَغيُّر خاصيّات العرض وخاصيات أبناء العرض الأخرى. هذا الحِمل ليس مصدرًا للقلق عادةً، لكن عند تنفيذ التحريكات المستمرة والاستجابة للإيماءات، فإن تحسين المكوّنات بحكمة يمكن أن يحسّن من دقة التحريكات.

إذا نظرت إلى شيفرة setNativeProps في NativeMethodsMixin ستلاحظ أنه غِلافٌ (wrapper) حول RCTUIManager.updateView. وهذا هو بالضبط نفس استدعاء الدالة الذي يُنتَج من إعادة التصيير. راجع receiveComponent في ReactNativeBaseComponent.js.

المكونات المركبة و setNativeProps

المكونات المركبَّة غير مدعومة من طرف عرضٍ أصيل، لذلك لا يمكنك استدعاء setNativeProps عليها. خذ هذا المثال:

import React from "react";
import { Text, TouchableOpacity, View } from "react-native";

const MyButton = (props) => (
  <View style={{ marginTop: 50 }}>
    <Text>{props.label}</Text>
  </View>
);

export default App = () => (
  <TouchableOpacity>
    <MyButton label="Press me!" />
  </TouchableOpacity>
);

إذا قمت بتشغيل هذا سترى هذا الخطأ فورًا: ‎‎Touchable child must either be native or forward setNativeProps to a native component‎‎. يحدث هذا لأنّ المكوّن MyButton غير مدعوم مباشرةً من طرف عرضٍ أصيل من المُفترَض أن تُعيَّن عتامته. يمكنك التفكير في الأمر كما يلي: إذا عرَّفتَ مكوِّنًا باستخدام createReactClass فلن تتوقع أن تكون قادرًا على تعيين خاصيّةِ ‎‎style‎‎ له لأنّها لن تعمَل، ستحتاج إلى تمرير الخاصيّة‎ style‎‎ ‎إلى مُكوِّنٍ ابن، إلا إذا كنت تُغلِّفُ (wrapping) عنصرًا أصيلًا. وبالمثل، سنقوم بإعادة توجيه setNativeProps إلى مكونٍ ابنٍ مدعومٍ من عرضٍ أصيل.

إعادة توجيه setNativeProps إلى مكون ابن

بما أن التابع setNativeProps متوافر لكل مرجع لمكوّن من النوع View فيكفي إعادة توجيه مرجع لمكونك المخصص إلى أحد المكونات <View /> المصيرة. وهذا يعني أن استدعاء الدالّة setNativeProps على مكون مخصص له نفس تأثير استدعاء setNativeProps على المكون View المغلِّف نفسه..

import React from "react";
import { Text, TouchableOpacity, View } from "react-native";

const MyButton = React.forwardRef((props, ref) => (
  <View {...props} ref={ref} style={{ marginTop: 50 }}>
    <Text>{props.label}</Text>
  </View>
));

export default App = () => (
  <TouchableOpacity>
    <MyButton label="Press me!" />
  </TouchableOpacity>
);

يمكنك الآن استخدام المكوّن MyButton داخل المكوّن TouchableOpacity!

قد تلاحظ أننا مررنا جميع الخاصيّات إلى العرض الابن باستخدام ‎‎{...this.props}‎‎. وسبب ذلك هو أن المكون TouchableOpacity هو في الواقع مكوّن مركب (composite component)، ولذا فبالإضافة إلى اعتماده على setNativeProps الموجودة في مكوّنه الابن، فإنه يتطلب أيضا أن يقوم المكون الابن بالتعامل مع اللمس. وللقيام بهذا فإنه يمرّر عدّة خاصيّاتٍ تستدعي مجدّدًا مكوّن TouchableOpacity. وعلى النقيض من ذلك، فإن TouchableHighlight مدعوم من عرض أصيل ولا يتطلب سوى إنشاء setNativeProps في المكوّن.

استعمال setNativeProps لمسح قيمة حقل إدخال النص

مسح قيمة المكوّن TextInput حالةٌ أخرى من حالات استخدام setNativeProps الشائعة. يمكن أحيانًا أن يؤدي استخدام الخاصيّة ‎‎controlled‎‎ التابعة للمكوّن TextInput إلى إسقاط المحارف عندما تكون الخاصيّة ‎‎bufferDelay‎‎ منخفضةً أو عندما يكتب المستخدم بسرعة كبيرة. يفضِّل بعض المطورين تخطي هذه الخاصيّة تمامًا واستخدامَ setNativeProps مباشرة بدلاً من ذلك لمعالجة قيمة المكوّن TextInput عند الضرورة. على سبيل المثال، توضح الشيفرة التالية كيفيّة مسح المدخلات عند النقر فوق زر:

import React from "react";
import { useCallback, useRef } from "react";
import { StyleSheet, TextInput, Text, TouchableOpacity, View } from "react-native";

const App = () => {
  const inputRef = useRef();
  const clearText = useCallback(() => {
    inputRef.current.setNativeProps({ text: "" });
  }, []);

  return (
    <View style={styles.container}>
      <TextInput ref={inputRef} style={styles.input} />
      <TouchableOpacity onPress={clearText}>
        <Text>Clear text</Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
  },
  input: {
    height: 50,
    width: 200,
    marginHorizontal: 20,
    borderWidth: 1,
    borderColor: "#ccc",
  },
});

export default App;

تجنب النزاعات مع دالة التصيير

عند تحديث خاصيةٍ تُدارُ أيضًا بواسطة دالة التصيير، فقد ينتهي الأمر إلى حدوث بعض العلل المربكة التي لا يمكن التنبؤ بها لأنه في كلّ مرّةٍ يُعاد فيها تصيير المكوّن وتتغيَّر هذه الخاصيّة، ستُتَجاهل أي قيمةٍ قد عُيِّنَت مسبقًا من setNativeProps بالكامل وستُتَجاوَز.

‎‎setNativeProps‎‎ و‎‎shouldComponentUpdate‎‎

يمكنك من خلال تطبيق ‎‎shouldComponentUpdate‎‎ بذكاءٍ تَجنّبُ عمليّات المطابقة غير الضرورية بين أشجار المكونات الفرعية (component subtrees)، إلى النقطة التي قد يكون فيها الأداء كافياً لاستخدام setState بدلاً من setNativeProps.

توابع أصيلة أخرى

تتوفر التوابع الموضّحة هنا على معظم المكونات الافتراضية التي يقدمها React Native. ومع ذلك، لاحظ أنها غير متوفرة على المكونات المركّبة التي لا تُدعم مباشرةً من طرف عرضٍ أصيل. سيتضمن هذا عمومًا معظم المكونات التي تُعرِّفها في تطبيقك الخاص.

‎‎measure(callback)‎‎

تُحدِّد الموقع على الشاشة والعرض والارتفاع للعرض المحدد في الإطار المعروض، وتُعيد القيم عبر دالّة رد نداء غير متزامنة (async callback). في حالة نجاح ذلك، ستُستدعَى دالّة رد النّداء باستخدام المعاملات التالية:

  • ‎‎x‎‎
  • ‎‎y‎‎
  • ‎‎width‎‎
  • ‎‎height‎‎
  • ‎‎pageX‎‎
  • ‎‎pageY‎‎

لاحظ أن هذه القياسات غير متوفّرة إلا بعد اكتمال التصيير على الجهة الأصيلة. إذا كنت بحاجة إلى القياسات في أقرب وقت ممكن ولا تحتاج pageX و pageY فانظر الخاصيّة ‎‎onLayout‎‎ كبديل.

العرض والارتفاع اللذان يعيدهما ()measure هما عرض وارتفاع المكون في الإطار المعروض، إذا كنت تريد حجم المكون الحقيقي فانظر استخدام الخاصية onLayoutكبديل.

‎‎measureInWindow(callback)‎‎

تُحدِّد موقع العرض المعطى في النافذة وتُعيد القيم عبر دالّة رد نداء غير متزامنة. إذا تم تضمين عرض React الجذر (React root view) في عرضٍ أصيلٍ آخر، فسوف تُعطيك هذه الدالة الإحداثيات المطلقة (absolute coordinates). في حالة نجاح ذلك، ستُستدعَى دالّة رد النّداء باستخدام المعاملات التالية:

  • ‎‎x‎‎
  • ‎‎y‎‎
  • ‎‎width‎‎
  • ‎‎height‎‎

‎‎measureLayout(relativeToNativeNode, onSuccess, onFail)‎‎

مشابهة للدالة ‎‎measure()‎‎، ولكنّها تقيس العرض نسبةً إلى مكوّنٍ جدٍّ (ancestor)، المحدَّد بالاسم ‎‎relativeToNativeNode‎‎. هذا يعني أن القيمتان ‎‎x‎‎ و‎‎y‎‎ نسبيّتان نسبةً إلى القيمتين الأصليتين ‎‎x‎‎ و‎‎y‎‎ الخاصّتين بالعرض الجد (ancestor view).

ملاحظة: يمكن استدعاء هذه الدالة مع معالج relativeToNativeNode أيضًا (بدلًا من المرجع)، لكن يهمل هذا الاختلاف.

import React, { useEffect, useRef, useState } from "react";
import { Text, View, StyleSheet } from "react-native";

const App = () => {
  const textContainerRef = useRef(null);
  const textRef = useRef(null);
  const [measure, setMeasure] = useState(null);

  useEffect(() => {
    if (textRef.current && textContainerRef.current) {
      textRef.current.measureLayout(
        textContainerRef.current,
        (left, top, width, height) => {
          setMeasure({ left, top, width, height });
        }
      );
    }
  }, [measure]);

  return (
    <View style={styles.container}>
      <View
        ref={textContainerRef}
        style={styles.textContainer}
      >
        <Text ref={textRef}>
          Where am I? (relative to the text container)
        </Text>
      </View>
      <Text style={styles.measure}>
        {JSON.stringify(measure)}
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
  },
  textContainer: {
    backgroundColor: "#61dafb",
    justifyContent: "center",
    alignItems: "center",
    padding: 12,
  },
  measure: {
    textAlign: "center",
    padding: 12,
  },
});

export default App;

‎‎focus()‎‎

تطلب التركيز على المدخل أو العرض المعطى. يعتمد السّلوك الذي سيُطلَق على نظام التشغيل ونوع العرض.

‎‎blur()‎‎

تُزيل التركيز على المدخل أو العرض المعطى. وهي عكس ‎‎focus()‎‎.

مصادر