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-native
نسخه و نسخه ای که می خواهید به آن ارتقا دهید. سپس تغییرات لازم را که باید در پایگاه کد خود ایجاد کنید، مشاهده خواهید کرد.
برای انصراف از معماری جدید در اندروید:
-
را باز کنید
android/gradle.properties
فایل -
را تغییر دهید
newArchEnabled
پرچم ازtrue
بهfalse
//gradle.properties
+newArchEnabled=false
برای انصراف از معماری جدید در iOS:
- را باز کنید
ios/Podfile
فایل -
اضافه کنید
ENV['RCT_NEW_ARCH_ENABLED'] = '0'
در محدوده اصلی پادفایل (مرجع پادفایل در قالب):+ ENV['RCT_NEW_ARCH_ENABLED']= '0' نیاز دارند غلاف::قابل اجرا.execute_command("گره"، ['-p', 'require.resolve)
-
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. UseEffect
in 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
Run the app
Start the Metro server:
npx react-native start
Open another terminal and run:
npx react-native run-android
or:
npx react-native run-ios
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;
}
We’ll also create another helper function for artificial delays:
function wait(ms) {
const end = Date.now() + ms;
while (Date.now() < end);
}
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 {};
}
}
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 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 خود دوباره ایجاد کنید
LogRocket یک راه حل نظارتی React Native است که به شما کمک می کند مشکلات را فوراً بازتولید کنید، اشکالات را اولویت بندی کنید و عملکرد برنامه های React Native خود را درک کنید.
LogRocket همچنین با نشان دادن نحوه تعامل کاربران با برنامه شما، به شما کمک می کند تا نرخ تبدیل و استفاده از محصول را افزایش دهید. ویژگی های تجزیه و تحلیل محصول LogRocket دلایلی را نشان می دهد که چرا کاربران یک جریان خاص را تکمیل نمی کنند یا یک ویژگی جدید را اتخاذ نمی کنند.
نظارت فعال بر برنامههای React Native خود را شروع کنید – LogRocket را به صورت رایگان امتحان کنید.