برنامه نویسی

React Native's New Architecture: Sync and async rendering

نوشته امانوئل جان✏️

React Native با انتشار New Architecture خود عملکرد قابل توجهی را افزایش داد.

New Architecture که اکنون پیش‌فرض نصب‌های جدید است، به شکایات دیرینه درباره سرعت و کارایی رسیدگی می‌کند. اگر از React Native 0.76 یا آخرین نسخه استفاده می‌کنید، این پیشرفت‌ها در حال حاضر در دسترس هستند و زمان هیجان‌انگیزی برای کشف این که این برای پروژه‌هایتان چه معنایی دارد، می‌سازد.

معماری جدید React Native با عملکرد بهتر، تجربه توسعه‌دهنده بهبود یافته و همسویی با ویژگی‌های مدرن React عرضه می‌شود.

این مقاله به بررسی موارد استفاده عملی برای رندر همزمان و ناهمزمان با معماری جدید می‌پردازد. ما همچنین معیارهای عملکرد را برای مقایسه معماری قدیمی و جدید ایجاد خواهیم کرد.

در زیر چند پیش نیاز وجود دارد که قبل از ادامه این مقاله به آنها نیاز دارید:

  • Node.js ≥v20 نصب شده است
  • دانش React
  • ساخت برنامه های کاربردی را با React Native تجربه کنید

معماری جدید چیست؟

New Architecture طراحی مجدد سیستم های داخلی React Native برای رفع چالش های موجود در معماری قدیمی است. این به روز رسانی ناهمزمان و همزمان را پشتیبانی می کند.

به طور سنتی، React Native برای اتصال جاوا اسکریپت و کد بومی به یک پل متکی بود. در حالی که این رویکرد به خوبی کار کرد، سربار را معرفی کرد. اکنون، New Architecture پل ناهمزمان بین جاوا اسکریپت و بومی را حذف کرده و آن را با رابط جاوا اسکریپت (JSI) جایگزین می کند. می‌تواند مستقیماً کدهای بومی C، C++ یا Kotlin (در اندروید) را بدون نیاز به پل زدن فراخوانی کند. این اجازه می دهد تا حافظه مشترک بین جاوا اسکریپت و لایه های بومی وجود داشته باشد که به طور قابل توجهی عملکرد را بهبود می بخشد.

زمانی که React Native با فناوری‌هایی مانند هرمس استاتیک، که جاوا اسکریپت را به اسمبلی کامپایل می‌کند، جفت شود، ایجاد برنامه‌های فوق‌العاده سریع را امکان‌پذیر می‌سازد.

یکی از مسائل رایج در معماری قدیمی، قابل مشاهده بودن حالت های میانی یا پرش های بصری بین رندر کردن طرح اولیه و به روز رسانی های بیشتر در چیدمان ها است.

تغییرات کلیدی در معماری جدید شامل به‌روزرسانی‌های طرح‌بندی همزمان، رندر همزمان، رابط جاوا اسکریپت (JSI) و پشتیبانی از ویژگی‌های پیشرفته React 18+ مانند انتقال تعلیق، دسته‌بندی خودکار و useLayoutEffect.

همچنین سازگاری با کتابخانه هایی را که معماری قدیمی را هدف قرار می دهند، امکان پذیر می کند.

راه اندازی معماری جدید

React Native 0.76 یا آخرین نسخه به طور پیش فرض با New Architecture عرضه می شود. اگر از Expo استفاده می کنید، React Native 0.76 اکنون در Expo SDK 52 پشتیبانی می شود.

اگر نیاز به معرفی معماری جدید در یک پایگاه کد قدیمی دارید، React Native Upgrade Helper ابزار مفیدی است که انتقال کد React Native خود را از یک نسخه به نسخه دیگر آسان می کند:

کمک کننده ارتقاء بومی react

تنها کاری که باید انجام دهید این است که جریان فعلی خود را وارد کنید react-native نسخه و نسخه ای که می خواهید به آن ارتقا دهید. سپس تغییرات لازم را که باید در پایگاه کد خود ایجاد کنید، مشاهده خواهید کرد.

برای انصراف از معماری جدید در اندروید:

  1. را باز کنید android/gradle.properties فایل

  2. را تغییر دهید newArchEnabled پرچم از true به false

//gradle.properties 
    +newArchEnabled=false 
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برای انصراف از معماری جدید در iOS:

  1. را باز کنید ios/Podfile فایل
  2. اضافه کنید ENV['RCT_NEW_ARCH_ENABLED'] = '0' در محدوده اصلی پادفایل (مرجع پادفایل در قالب):

    + ENV['RCT_NEW_ARCH_ENABLED']= '0'
    نیاز دارند غلاف::قابل اجرا.execute_command("گره"، ['-p',  
     'require.resolve)
    
  3. Install your CocoaPods dependencies with the command:

    bundle exec pod install
    

To understand async and sync rendering in React Native, you should be familiar with UseLayoutEffect vs. UseEffectin React.

Asynchronous layout and effects

One of the most common issues with the legacy architecture was the visual glitches during layout changes. This is because developers needed to use the asynchronous onLayout event to read layout information of a view (which was also asynchronous). This caused at least one frame to render an incorrect layout before it could be read and updated.

The New Architecture solves this issue by allowing synchronous access to layout information and ensuring properly scheduled updates. This way, users never see any intermediate state.

To experience the improvements in performance and user experience provided by the New Architecture, we’ll build an adaptive tooltip using the legacy architecture to experience the visual glitches.

In the next section, we’ll build the same using the New Architecture. You’ll see that the tooltip will align perfectly without intermediate state jumps, which solves the visual glitches issue that causes a poor user experience.

Project setup

Ensure you have a React Native environment configured. Check out the React Native CLI Quickstart guide if you haven’t done this.

Run the following in your project folder:

npx react-native init ToolTipApp
cd ToolTipApp
Enter fullscreen mode

Exit fullscreen mode

Run the app

Start the Metro server:

npx react-native start
Enter fullscreen mode

Exit fullscreen mode

Open another terminal and run:

npx react-native run-android
Enter fullscreen mode

Exit fullscreen mode

or:

npx react-native run-ios
Enter fullscreen mode

Exit fullscreen mode

Helper functions

We’ll implement two helper functions to calculate the x and y positions of the tooltip based on:

  • The dimensions and position of the tooltip (toolTip)
  • The target element (target)
  • The boundaries of the root view (rootView)

In the src directory, create a utils folder. Inside it, add a new file named helper.js and include the following code:

export function calculateX(toolTip, target, rootView) {
  let toolTipX = target.x + target.width / 2 - toolTip.width / 2; 
  if (toolTipX < rootView.x) {
    toolTipX = target.x; 
  }
  if (toolTipX + toolTip.width > rootView.x + rootView.width) {
    toolTipX = rootView.x + rootView.width - toolTip.width; 
  }
  return toolTipX - rootView.x; 
}

export function calculateY(toolTip, target, rootView) {
  let toolTipY = target.y - toolTip.height; 
  if (toolTipY < rootView.y) {
    toolTipY = target.y + target.height; 
  }
  return toolTipY - rootView.y;
}
Enter fullscreen mode

Exit fullscreen mode

We’ll also create another helper function for artificial delays:

function wait(ms) {
  const end = Date.now() + ms;
  while (Date.now() < end);
}
Enter fullscreen mode

Exit fullscreen mode

Dynamic styling based on position

We’ll create another helper function getStyle which returns the appropriate alignment styles for each tooltip position:

function getStyle(position) {
  switch (position) {
    case 'top-left':
      return { justifyContent: 'flex-start', alignItems: 'flex-start' };
    case 'center-center':
      return { justifyContent: 'center', alignItems: 'center' };
    case 'bottom-right':
      return { justifyContent: 'flex-end', alignItems: 'flex-end' };
    default:
      return {};
  }
}
Enter fullscreen mode

Exit fullscreen mode

ToolTip component

The ToolTip component measures its dimensions (rect) asynchronously after layout and dynamically updates its position using the calculateX and calculateY functions.

In the src directory, create a components folder. Inside it, add a new file named ToolTip.jsx and include the following code:

import * as React from 'react';
import {View} from 'react-native';
import {calculateX, calculateY} from '../utils/helper'

function ToolTip({ position, targetRect, rootRect, children }) {
  const ref = React.useRef(null);
  const [rect, setRect] = واکنش نشان دهید.استفاده از ایالت(تهی)

  پایان onLayout = واکنش نشان دهید.استفاده از تماس برگشتی(() => {
    رجوع کنید.جاری?.اندازه گیری در پنجره((x، y، عرض، ارتفاع) => {
      setRect({ x، y، عرض، ارتفاع })؛
    })؛
   [])

  اجازه دهید سمت چپ = 0;
  اجازه دهید بالا = 0;

  اگر (راست && targetRect && rootRect) {
    سمت چپ = محاسبه X(راست، targetRect، rootRect) 
    بالا = محاسبه Y(راست، targetRect، rootRect) 
  }

  بازگشت (
    <مشاهده کنید
      رجوع کنید={رجوع کنید}
      onLayout={onLayout}
      سبک={{
        موقعیت: 'مطلق'،
        مرز رنگ: 'سبز'،
        عرض مرز: 2،
        شعاع مرزی: 8،
        بالشتک: 4،
        بالا،
        سمت چپ،
      }}
    >
      {کودکان}
    </نمایش>
  )
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما از a استفاده می کنیم ref برای ذخیره یک مرجع به View عنصر، به ما امکان می دهد ابعاد و موقعیت آن را روی صفحه اندازه گیری کنیم. این onLayout هر زمان که طرح‌بندی صفحه‌آرایی انجام شود، پاسخ به تماس فعال می‌شود View تغییر می کند. در این فراخوانی، measureInWindow متد راهنمای ابزار را بازیابی می کند x، y، width، و height، که سپس در ذخیره می شوند rect دولت

Targetجزء

این Target کامپوننت ابعاد آن را اندازه گیری کرده و به آن منتقل می کند ToolTip جزء

در components دایرکتوری، یک فایل جدید به نام اضافه کنید Target.jsx و کد زیر را شامل شود:

import * as React from 'react';
import {Pressable, Text, View} from 'react-native';
import ToolTip from './ToolTip'

function Target({ toolTipText, targetText, position, rootRect }) {
  const targetRef = React.useRef(null);
  const [rect, setRect] = React.useState(null);

  const onLayout = React.useCallback(() => {
    targetRef.current?.measureInWindow((x, y, width, height) => {
      setRect({ x, y, width, height });
    });
  }, []);

  return (
    <>
      <View
        ref={targetRef}
        onLayout={onLayout}
        style={{
          borderColor: 'red',
          borderWidth: 2,
          padding: 10,
        }}
      >
        <Text>{targetText}</Text>
      </View>
      <ToolTip position={position} rootRect={rootRect} targetRect={rect}>
        <Text>{toolTipText}</Text>
      </ToolTip>
    </>
  );
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

استفاده می کنیم useCallback برای به دست آوردن اندازه‌گیری‌های نما و سپس به‌روزرسانی موقعیت راهنمای ابزار بر اساس مکان نما.

جزء نمایشی

این جزء به صورت پویا موقعیت a را به روز می کند Target راهنمای کامپوننت در هر ثانیه، در موقعیت های مختلف راهنمای ابزار می چرخد، و ابعاد نمای ریشه را برای محاسبه موقعیت های نسبی راهنمای ابزار اندازه گیری می کند.

در components دایرکتوری، یک فایل جدید به نام اضافه کنید Demo.jsx و کد زیر را شامل شود:

import * as React from 'react';
import {Text, View} from 'react-native';
import Target from './Target'

export function Demo() {
  const positions = ['top-left', 'top-right', 'center-center', 'bottom-left', 'bottom-right'];
  const [index, setIndex] = React.useState(0);
  const [rect, setRect] = React.useState(null);
  const ref = React.useRef(null);

  React.useEffect(() => {
    const interval = setInterval(() => {
      setIndex((prevIndex) => (prevIndex + 1) % positions.length); 
    }, 1000);
    return () => clearInterval(interval);
  }, []);

  const onLayout = React.useCallback(() => {
    ref.current?.measureInWindow((x, y, width, height) => {
      setRect({ x, y, width, height });
    });
  }, []);

  const position = positions[index];
  const style = getStyle(position);

  return (
    <>
      <Text style={{ margin: 20 }}>Position: {position}</Text>
      <View ref={ref} onLayout={onLayout} style={{ ...style, flex: 1, borderWidth: 1 }}>
        <Target toolTipText="This is the tooltip" targetText="This is the target" position={position} rootRect={rect} />
      </View>
    </>
  );
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در useEffect هوک، فاصله‌ای را برای افزایش شاخص موقعیت در هر ثانیه تنظیم می‌کنیم و زمانی که به انتهای آرایه رسید آن را بازنشانی می‌کنیم. الف را نیز پیوست کردیم ref به ریشه View ظرف و استفاده کرد measureInWindow روش در onLayout پاسخ تماس برای گرفتن x، y، width، و height از ظرف ریشه این اطلاعات در rect دولت و به تصویب رسید Target جزء، و آن را قادر می سازد تا نوک ابزار خود را نسبت به محفظه ریشه قرار دهد.

در اینجا کامپوننت دمو شما باید شبیه باشد:

مؤلفه نمایشی

به تفاوت زمانی بین حرکت راهنمای ابزار و جزء هدف توجه کنید. این مشکل بصری است. برای تجربه کاربری بهتر، هر دو مؤلفه باید همزمان با هم حرکت کنند.

چیدمان و جلوه های همزمان

ما می‌توانیم با دسترسی همزمان به اطلاعات طرح‌بندی و به‌روزرسانی‌های برنامه‌ریزی‌شده به‌طور کامل از مشکلات بصری جلوگیری کنیم، به طوری که هیچ حالت میانی برای کاربران قابل مشاهده نباشد.

با معماری جدید می توانیم استفاده کنیم [useLayoutEffect](https://react.dev/reference/react/useLayoutEffect) برای اندازه‌گیری و اعمال به‌روزرسانی‌های طرح‌بندی به طور همزمان در یک commit، از «پرش» بصری اجتناب کنید.

ToolTip جزء

این مؤلفه به صورت پویا راهنمای ابزار را بر اساس قرار می دهد targetRect، rootRectو ابعاد خاص آن:

export function ToolTip({position, targetRect, rootRect, children}) {
  const ref = React.useRef(null);
  const [rect, setRect] = React.useState(null);

  React.useLayoutEffect(() => {
    wait(200); // Simulate delay
    setRect(ref.current?.getBoundingClientRect());
  }, [setRect, position]);

  let left = 0, top = 0;
  if (rect && targetRect && rootRect) {
    left = calculateX(rect, targetRect, rootRect);
    top = calculateY(rect, targetRect, rootRect);
  }

  return (
    <View
      ref={ref}
      style={{
        position: 'absolute',
        borderColor: 'green',
        borderRadius: 8,
        borderWidth: 2,
        padding: 4,
        top,
        left,
      }}>
      {children}
    </View>
  );
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در useLayoutEffect هوک، ما یک تاخیر را شبیه سازی می کنیم (با استفاده از wait function) و سپس با فراخوانی موقعیت راهنمای ابزار را به روز کنید getBoundingClientRect() در مورد ارجاع شده View عنصر این اطلاعات در rect حالت و برای محاسبه موقعیت راهنمای ابزار نسبت به عنصر هدف و ظرف ریشه با استفاده از calculateX و calculateY توابع

Target جزء

این عنصر هدف را نشان می دهد و راهنمای ابزار را نسبت به خودش رندر می کند. با استفاده از ابعاد آن را محاسبه می کند getBoundingClientRect.

function Target({toolTipText, targetText, position, rootRect}) {
  const targetRef = React.useRef(null);
  const [rect, setRect] = React.useState(null);

  React.useLayoutEffect(() => {
    wait(200); // Simulate delay
    setRect(targetRef.current?.getBoundingClientRect());
  }, [setRect, position]);

  return (
    <>
      <View
        ref={targetRef}
        style={{
          borderColor: 'red',
          borderWidth: 2,
          padding: 10,
        }}>
        <Text>{targetText}</Text>
      </View>
      <ToolTip position={position} rootRect={rootRect} targetRect={rect}>
        <Text>{toolTipText}</Text>
      </ToolTip>
    </>
  );
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

استفاده می کنیم useRef برای ایجاد یک مرجع به عنصر هدف (targetRef) و useState برای ذخیره ابعاد و موقعیت آن (rect). در useLayoutEffect هوک، ما یک تاخیر را با استفاده از آن شبیه سازی می کنیم wait تابع، سپس به روز رسانی rect ایالت با تماس getBoundingClientRect() روی عنصر هدف برای ثبت موقعیت و اندازه آن.

Demo جزء

این مؤلفه با چرخش در موقعیت های از پیش تعریف شده در هر ثانیه، تغییر موقعیت پویا راهنمای ابزار را نشان می دهد:

function Demo() {
  const toolTipText = 'This is the tooltip';
  const targetText = 'This is the target';
  const ref = React.useRef(null);
  const [index, setIndex] = React.useState(0);
  const [rect, setRect] = React.useState(null);

  React.useEffect(() => {
    const setPosition = setInterval(() => {
      setIndex((index + 1) % positions.length); // Cycle positions
    }, 1000);

    return () => clearInterval(setPosition);
  }, [index]);

  const position = positions[index];
  const style = getStyle(position);

  React.useLayoutEffect(() => {
    wait(200);
    setRect(ref.current?.getBoundingClientRect());
  }, [setRect, position]);

  return (
    <>
      <Text style={{margin: 20}}>Position: {position}</Text>
      <View
        style={{...style, flex: 1, borderWidth: 1}}
        ref={ref}>
        <Target
          toolTipText={toolTipText}
          targetText={targetText}
          rootRect={rect}
          position={position}
        />
      </View>
    </>
  );
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

یک متغیر حالت را مقداردهی اولیه می کنیم index برای ردیابی موقعیت فعلی، که از طریق چرخه positions آرایه هر ثانیه با استفاده از setInterval در یک useEffect قلاب. این position به روز می شود و برای محاسبه سبک چیدمان برای ریشه استفاده می شود View ظرف با استفاده از getStyle تابع

این useLayoutEffect از قلاب برای ثبت ابعاد و موقعیت ظرف ریشه استفاده می شود (ref) پس از یک تاخیر شبیه سازی شده، ذخیره اطلاعات در rect دولت این rect سپس به منتقل می شود Target جزء برای قرار دادن راهنمای ابزار نسبت به محفظه ریشه.

در اینجا کامپوننت دمو شما باید شبیه باشد:

مولفه نمایشی رندر همگام سازی بومی react

معیارهای عملکرد معماری جدید

تیم React Native اپلیکیشنی ایجاد کرده است که سناریوهای مختلف عملکرد را در یک مکان ترکیب می کند. این برنامه مقایسه معماری قدیمی و جدید و شناسایی شکاف های عملکردی در معماری جدید را آسان تر می کند.

در این بخش، معیارهایی را برای ارزیابی تفاوت‌های عملکردی بین معماری قدیمی و جدید ایجاد و اجرا می‌کنیم.

برای شروع، دستور زیر را برای کلون کردن برنامه اجرا کنید:

git clone --branch new-architecture-benchmarks https://github.com/react-native-community/RNNewArchitectureApp
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سپس، وابستگی ها را نصب کنید:

cd RNNewArchitectureApp/App
yarn install
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

دستور زیر را برای پیکربندی پروژه برای استفاده از این معماری جدید اجرا کنید:

RCT_NEW_ARCH_ENABLED=1 npx pod-install
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

حرکت به ios دایرکتوری:

cd ios
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

باز کنید MeasurePerformance.xcworkspace. را فشار دهید CMD + I برای ساخت بهینه یا CMD + R برای ساخت اشکال زدایی

برای اندروید، دستور زیر را اجرا کنید تا برنامه را با بهینه سازی بسازید:

yarn android --mode release
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

شما همچنین می توانید اجرا کنید yarn android برای ساخت برنامه در حالت اشکال زدایی.

در اینجا برنامه در حال اجرا شما باید شبیه باشد:

برنامه اجرای معماری قدیمی

زمان معماری قدیمی برای دویدن

روی هر دکمه کلیک کنید تا ببینید چقدر طول می کشد تا اجزای مربوطه را ارائه کنید.

بعد، به معماری جدید برگه، فرآیند را تکرار کنید و نتایج را با هم مقایسه کنید.

در زیر مقایسه ای از نتایج من است:

شبیه ساز مجازی: Google Pixel 5 API 33

سناریو معماری قدیمی معماری جدید تفاوت
1500 282 میلی‌ثانیه 252 میلی‌ثانیه معماری جدید 8٪ سریعتر است
5000 1088 میلی‌ثانیه 1035 میلی‌ثانیه معماری جدید 4٪ سریعتر است
1500 512 میلی‌ثانیه 503 میلی‌ثانیه معماری جدید 1٪ سریعتر است
5000 2156 میلی‌ثانیه 2083 میلی‌ثانیه معماری جدید 3٪ سریعتر است
1500 406 میلی‌ثانیه 402 میلی‌ثانیه معماری جدید با معماری قدیمی خنثی است
5000 1414 میلی‌ثانیه 1378 میلی‌ثانیه معماری جدید 3٪ سریعتر است

نتیجه گیری

در این مقاله، رندر همزمان و ناهمزمان را در React Native از طریق موارد استفاده عملی بررسی کردیم و عملکرد معماری قدیمی و جدید را با هم مقایسه کردیم. با نتایج معیار، ما می توانیم مزایای قابل توجه اتخاذ این معماری جدید را ببینیم. اگر از React Native 0.76 یا بالاتر استفاده می‌کنید، New Architecture قبلاً پشتیبانی می‌شود و خارج از جعبه کار می‌کند و نیازی به پیکربندی اضافی ندارد.


LogRocket: فوراً مشکلات را در برنامه های React Native خود دوباره ایجاد کنید

فوراً مشکلات را در برنامه های React Native خود دوباره ایجاد کنید

LogRocket یک راه حل نظارتی React Native است که به شما کمک می کند مشکلات را فوراً بازتولید کنید، اشکالات را اولویت بندی کنید و عملکرد برنامه های React Native خود را درک کنید.

LogRocket همچنین با نشان دادن نحوه تعامل کاربران با برنامه شما، به شما کمک می کند تا نرخ تبدیل و استفاده از محصول را افزایش دهید. ویژگی های تجزیه و تحلیل محصول LogRocket دلایلی را نشان می دهد که چرا کاربران یک جریان خاص را تکمیل نمی کنند یا یک ویژگی جدید را اتخاذ نمی کنند.

نظارت فعال بر برنامه‌های React Native خود را شروع کنید – LogRocket را به صورت رایگان امتحان کنید.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا