Hacktoberfest 2026: die Issues, die Maintainer für den Oktober markiert haben – offen und einsteigerfreundlich. Hacktoberfest-Issues durchsuchen

Every styled element costs about 2.8x a React Native one to mount: a wrapper component, a rule pass and effects, with or without a `className`

Offen
#546 0 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Maintainer antworten meist innerhalb von 3 Tagen

Dieses Issue hat noch niemand übernommen.

Bewertung

Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Anfängerfreundlichkeit
48/100
Issue-Typ
Bug
Klarheit
Klar beschrieben
Aktivitätsstatus
Aktiv
Tech-Stack
react, react-native, typescript
Bereich
mobile, performance

Rechercherichtung

Start with src/components/View.tsx, src/native/api.tsx, and src/native/react/useNativeCss.ts, which the issue identifies as the wrapper and mount-time work. Read the mount-cost.test.tsx reproducer on branch repro/546-per-element-mount-cost and run it with yarn jest src/__tests__/native/mount-cost.test.tsx. Done means elements without a className skip the rule pass and effects, while styled elements mount near the cost of their React Native counterparts.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Beschreibung

Before submitting a new issue
  • I tested using the latest version of the library, as the bug might be already fixed.
  • I tested using a supported version of react native.
  • I checked for possible duplicate issues, with possible answers.
Bug summary

Each styled component is a wrapper that calls useCssElement(Component, props, mapping) (src/components/View.tsx and its siblings) and returns createElement(Component, props), so every element mounts one component instance more than the component it styles. Every element then pays at mount, with a className or without one: useCssElement's useState(() => mappingToConfig(mapping)) (src/native/api.tsx), two useContext reads in useNativeCss (src/native/react/useNativeCss.ts), a useState initializer that builds two Effect objects, each holding a Set, and runs updateRules, a useRef and a passive useEffect, and testGuards and getStyledProps on every render.

A styled View takes about 2.8x a React Native View to mount in this harness, and as much without a className as with one, since the rule pass runs once per element either way.

Expected: an element without a className costs what the component it wraps costs, and one with a className pays the rule pass and nothing it does not use.

Library version

main at 2421ea7 (0.0.0-nightly.2421ea7)

Environment info
System:
  OS: Windows 11 10.0.26300
  CPU: "(32) x64 AMD Ryzen 9 9950X3D 16-Core Processor          "
  Memory: 155.26 GB / 255.67 GB
Binaries:
  Node:
    version: 26.12.1
    path: C:\Program Files\nodejs\node.EXE
  Yarn:
    version: 4.9.2
    path: C:\Users\<user>\AppData\Roaming\npm\yarn.CMD
  npm:
    version: 12.2.1
    path: C:\Program Files\nodejs\npm.CMD
  Watchman: Not Found
SDKs:
  Android SDK:
    API Levels:
      - "33"
      - "35"
      - "35"
      - "35"
      - "36"
      - "37"
    Build Tools:
      - 35.0.0
      - 36.0.0
      - 36.1.0
      - 37.0.0
    System Images:
      - android-26 | Google APIs Intel x86_64 Atom
      - android-28 | Google APIs Intel x86_64 Atom
      - android-29 | Google APIs Intel x86_64 Atom
      - android-30 | Google APIs Intel x86_64 Atom
      - android-31 | Google APIs Intel x86_64 Atom
      - android-32 | Google APIs Intel x86_64 Atom
      - android-33 | Google APIs Intel x86_64 Atom
      - android-34 | Google APIs Intel x86_64 Atom
      - android-35 | Google APIs Intel x86_64 Atom
      - android-36.1 | Google Play Intel x86_64 Atom
      - android-36.1 | Pre-Release 16 KB Page Size Google Play Intel x86_64 Atom
      - android-37.0 | Google APIs Intel x86_64 Atom
    Android NDK: Not Found
  Windows SDK: Not Found
IDEs:
  Android Studio: AI-262.9437.185.2621.16467767
  Visual Studio: Not Found
Languages:
  Java:
    version: 17.0.20.1
    path: C:\Program Files\Microsoft\jdk-17.0.20.101-hotspot\bin\javac.EXE
  Ruby: Not Found
npmPackages:
  "@react-native-community/cli": Not Found
  react:
    installed: 19.2.3
    wanted: 19.2.3
  react-native:
    installed: 0.86.3
    wanted: 0.86.3
  react-native-windows: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: Not found
  newArchEnabled: Not found
iOS:
  hermesEnabled: Not found
  newArchEnabled: Not found
Steps to reproduce
  1. Clone the reproducer branch, which is main at 2421ea7 with one file added, src/__tests__/native/mount-cost.test.tsx, and install:
    git clone --branch repro/546-per-element-mount-cost https://github.com/YevheniiKotyrlo/react-native-css
    cd react-native-css
    yarn install
    
  2. Run yarn jest src/__tests__/native/mount-cost.test.tsx.

src/__tests__/native/mount-cost.test.tsx:

import { View as RNView, type ViewProps } from "react-native";

import { performance } from "node:perf_hooks";

import { render } from "@testing-library/react-native";
import { View } from "react-native-css/components/View";
import { registerCSS } from "react-native-css/jest";

import * as rules from "../../native/react/rules";

const ELEMENTS = 500;
const WARM_UP_RUNS = 1;
const RUNS = 25;

const ARMS = [
  "react-native View",
  "react-native View in a function component",
  "css View, no className",
  "css View, className",
] as const;

type Arm = (typeof ARMS)[number];

const STYLE = { backgroundColor: "red", padding: 8 } as const;

function WrappedView(props: ViewProps) {
  return <RNView {...props} />;
}

function renderArm(arm: Arm) {
  const children = Array.from({ length: ELEMENTS }, (_, index) => {
    switch (arm) {
      case "react-native View":
        return <RNView key={index} style={STYLE} />;
      case "react-native View in a function component":
        return <WrappedView key={index} style={STYLE} />;
      case "css View, no className":
        return <View key={index} style={STYLE} />;
      case "css View, className":
        return <View key={index} className="box" />;
    }
  });
  return render(<RNView>{children}</RNView>);
}

/** react-test-renderer's instance tree holds composite and host components alike. */
interface ComponentInstance {
  readonly children: readonly (string | ComponentInstance)[];
}

function countComponentInstances(instance: ComponentInstance): number {
  return instance.children.reduce<number>(
    (count, child) =>
      typeof child === "string"
        ? count
        : count + countComponentInstances(child),
    1,
  );
}

function median(values: readonly number[]): number {
  const sorted = [...values].sort((left, right) => left - right);
  return sorted[Math.floor(sorted.length / 2)] ?? Number.NaN;
}

beforeEach(() => {
  registerCSS(`.box { background-color: red; padding: 8px; }`);
});

afterEach(() => {
  jest.restoreAllMocks();
});

test("a css View mounts one more component instance than a react-native View", () => {
  const instancesOf = (arm: Arm): number => {
    const result = renderArm(arm);
    const count = countComponentInstances(result.UNSAFE_root);
    result.unmount();
    return count;
  };

  const reactNative = instancesOf("react-native View");
  const wrapped = instancesOf("react-native View in a function component");
  const cssWithoutClassName = instancesOf("css View, no className");
  const cssWithClassName = instancesOf("css View, className");
  console.log(
    [
      `react-native View: ${String(reactNative)} instances for ${String(ELEMENTS)} elements`,
      `react-native View in a function component: ${String(wrapped)}`,
      `css View, no className: ${String(cssWithoutClassName)}`,
      `css View, className: ${String(cssWithClassName)}`,
    ].join("\n"),
  );

  expect(wrapped).toBe(reactNative + ELEMENTS);
  expect(cssWithoutClassName).toBe(reactNative + ELEMENTS);
  expect(cssWithClassName).toBe(reactNative + ELEMENTS);
});

test("every css View runs the rule pass when it mounts, className or not", () => {
  const updateRules = jest.spyOn(rules, "updateRules");
  const rulePassesOf = (arm: Arm): number => {
    updateRules.mockClear();
    renderArm(arm).unmount();
    return updateRules.mock.calls.length;
  };

  const withoutClassName = rulePassesOf("css View, no className");
  const withClassName = rulePassesOf("css View, className");
  console.log(
    [
      `css View, no className: ${String(withoutClassName)} rule passes for ${String(ELEMENTS)} elements`,
      `css View, className: ${String(withClassName)} rule passes for ${String(ELEMENTS)} elements`,
    ].join("\n"),
  );

  expect(withoutClassName).toBe(ELEMENTS);
  expect(withClassName).toBe(ELEMENTS);
});

test("the mount cost per element, react-native View against css View", () => {
  const samples: Record<Arm, number[]> = {
    "react-native View": [],
    "react-native View in a function component": [],
    "css View, no className": [],
    "css View, className": [],
  };

  for (let run = 0; run < WARM_UP_RUNS + RUNS; run += 1) {
    const rotation = run % ARMS.length;
    for (const arm of [...ARMS.slice(rotation), ...ARMS.slice(0, rotation)]) {
      const start = performance.now();
      const result = renderArm(arm);
      const elapsed = performance.now() - start;
      result.unmount();
      if (run >= WARM_UP_RUNS) {
        samples[arm].push(elapsed);
      }
    }
  }

  const baseline = median(samples["react-native View"]);
  console.log(
    [
      `mounting ${String(ELEMENTS)} elements, median of ${String(RUNS)} runs (Node ${process.version}, NODE_ENV=${process.env.NODE_ENV}, arm order rotated per run):`,
      ...ARMS.map((arm) => {
        const milliseconds = median(samples[arm]);
        const perElement = (milliseconds * 1000) / ELEMENTS;
        const spread = `${Math.min(...samples[arm]).toFixed(1)}-${Math.max(...samples[arm]).toFixed(1)} ms`;
        return `  ${arm}: ${milliseconds.toFixed(1)} ms (${spread}), ${perElement.toFixed(1)} µs per element, ${(milliseconds / baseline).toFixed(2)}x`;
      }),
    ].join("\n"),
  );

  for (const arm of ARMS) {
    expect(samples[arm]).toHaveLength(RUNS);
  }
});

Expected: an element mounts at close to the cost of the React Native component it styles, and one without a className skips the rule pass and its effects.

Actual: every element mounts a wrapper, runs a rule pass and effects, with or without a className, at about 2.8 times a React Native element's cost.

The run's output on main at 2421ea7, Windows 11, Node 26.12.1
  console.log
    react-native View: 1002 instances for 500 elements
    react-native View in a function component: 1502
    css View, no className: 1502
    css View, className: 1502
      at Object.log (src/__tests__/native/mount-cost.test.tsx:86:11)
  console.log
    css View, no className: 500 rule passes for 500 elements
    css View, className: 500 rule passes for 500 elements
      at Object.log (src/__tests__/native/mount-cost.test.tsx:110:11)
  console.log
    mounting 500 elements, median of 25 runs (Node v26.12.1, NODE_ENV=test, arm order rotated per run):
      react-native View: 3.8 ms (3.7-7.1 ms), 7.6 µs per element, 1.00x
      react-native View in a function component: 5.1 ms (4.9-14.0 ms), 10.2 µs per element, 1.34x
      css View, no className: 8.5 ms (8.0-25.0 ms), 17.1 µs per element, 2.25x
      css View, className: 12.6 ms (9.3-29.0 ms), 25.2 µs per element, 3.32x
      at Object.log (src/__tests__/native/mount-cost.test.tsx:143:11)
PASS src/__tests__/native/mount-cost.test.tsx
  √ a css View mounts one more component instance than a react-native View (95 ms)
  √ every css View runs the rule pass when it mounts, className or not (41 ms)
  √ the mount cost per element, react-native View against css View (961 ms)
Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        2.116 s, estimated 3 s
Ran all test suites matching /src\\__tests__\\native\\mount-cost.test.tsx/i.
Reproducible example repository

https://github.com/YevheniiKotyrlo/react-native-css/tree/repro/546-per-element-mount-cost

Vorherrschende Sprache
TypeScript
Sterne
166
Forks
45
Ø Merge
13 T. 14 Std.
Gemergte PRs (30 T.)
1

Entwicklungsumgebung

Erste Schritte

  1. Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
  3. Forken Sie das Repository und arbeiten Sie in einem Branch.
  4. Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.

Mehr aus nativewind/react-native-css

Alle Issues in nativewind/react-native-css

Ähnliche Issues

Weitere Issues zu TypeScript

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.