React(Expo)与React Native项目共享代码及创建共享库的最简方案咨询
Hey there! Awesome plan—sharing code between React web and React Native is a fantastic way to keep your brand consistent and avoid repeating work. Let’s walk through the simplest, most straightforward implementation that fits your exact project structure request.
First, lay out your folders exactly as you described. This keeps everything organized and makes it easy to distinguish shared vs platform-specific code:
your-project/ ├── web/ # Your React web app (use Create React App or Vite) │ ├── src/ │ └── package.json ├── mobile/ # Your React Native app (Expo or React Native CLI works) │ ├── src/ │ └── package.json └── shared/ # All shared code lives here ├── components/ │ └── Button/ # Shared Button component folder └── constants/ # Shared brand assets
Put all your brand-specific values (like colors, font weights, or spacing) in the shared/constants folder. This ensures your brand stays consistent across both platforms without duplication.
Create shared/constants/brand.js:
export const BRAND_COLORS = { primary: "#2563eb", // Your main brand blue secondary: "#10b981", // A complementary green error: "#ef4444", // Error state red white: "#ffffff", }; export const FONT_WEIGHTS = { regular: 400, bold: 700, };
The key here is separating shared logic/props from platform-specific rendering. We’ll use React’s platform-specific file suffixes (.web.jsx and .native.jsx) to let each platform load its own button implementation, while keeping the core logic shared.
3.1 Shared Button Logic File
Create shared/components/Button/Button.jsx—this is the "shell" that handles shared props, logic, and passes data to the platform-specific button:
import { BRAND_COLORS } from "../../constants/brand"; // Dynamically import the platform-specific button (React/React Native auto-detects suffixes) import ButtonImpl from "./Button.platform"; export default function Button({ children, variant = "primary", onPress, ...rest }) { // Shared logic: Get the correct brand color based on the variant const bgColor = BRAND_COLORS[variant]; // Shared handler: Wrap onPress to add any cross-platform logic (e.g., loading states) const handlePress = () => { if (onPress) onPress(); }; // Pass shared values and props to the platform-specific button return ( <ButtonImpl bgColor={bgColor} onPress={handlePress} {...rest} > {children} </ButtonImpl> ); }
3.2 Web-Specific Button Rendering
Create shared/components/Button/Button.web.jsx—this uses the standard HTML <button> element:
import { BRAND_COLORS, FONT_WEIGHTS } from "../../constants/brand"; export default function ButtonWeb({ bgColor, onPress, children, ...rest }) { return ( <button style={{ backgroundColor: bgColor, color: BRAND_COLORS.white, padding: "10px 20px", border: "none", borderRadius: "6px", cursor: "pointer", fontSize: "16px", fontWeight: FONT_WEIGHTS.bold, }} onClick={onPress} {...rest} > {children} </button> ); }
3.3 Native-Specific Button Rendering
Create shared/components/Button/Button.native.jsx—this uses React Native’s TouchableOpacity (the standard native button component):
import { TouchableOpacity, Text, StyleSheet } from "react-native"; import { BRAND_COLORS, FONT_WEIGHTS } from "../../constants/brand"; export default function ButtonNative({ bgColor, onPress, children, ...rest }) { return ( <TouchableOpacity style={[styles.button, { backgroundColor: bgColor }]} onPress={onPress} {...rest} > <Text style={styles.text}>{children}</Text> </TouchableOpacity> ); } const styles = StyleSheet.create({ button: { paddingVertical: 10, paddingHorizontal: 20, borderRadius: 6, }, text: { color: BRAND_COLORS.white, fontSize: 16, fontWeight: FONT_WEIGHTS.bold, textAlign: "center", }, });
To make importing easy, add the shared folder as a local dependency in both your web and mobile projects:
- In
web/package.json, add:"dependencies": { // ... your existing web dependencies "shared": "file:../shared" } - In
mobile/package.json, add the same line.
Now you can import the shared Button component in either project like this:
Web Example (web/src/App.jsx)
import Button from "shared/components/Button/Button"; function App() { return ( <div style={{ padding: "2rem" }}> <Button variant="primary" onPress={() => alert("Web primary button clicked!")}> Web Primary Button </Button> <Button variant="secondary" onPress={() => alert("Web secondary button clicked!")} style={{ marginTop: "1rem" }}> Web Secondary Button </Button> </div> ); } export default App;
Native Example (mobile/App.jsx)
import { View, StyleSheet } from "react-native"; import Button from "shared/components/Button/Button"; export default function App() { return ( <View style={styles.container}> <Button variant="primary" onPress={() => alert("Native primary button clicked!")}> Native Primary Button </Button> <Button variant="secondary" onPress={() => alert("Native secondary button clicked!")} style={{ marginTop: 16 }}> Native Secondary Button </Button> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: "center", alignItems: "center", padding: 20, }, });
- Keep shared code platform-agnostic: Only put logic, constants, or component shells in
shared—avoid anything that relies on web APIs (likewindow) or native APIs (likeAlertdirectly) unless you wrap them in platform-specific modules. - Expo users win: Expo automatically detects
.web.jsxand.native.jsxsuffixes, so no extra config is needed. For React Native CLI, you might need to tweak your Babel config to handle platform files. - Add TypeScript (optional but helpful): If you use TypeScript, you can define shared prop types in the
sharedfolder to ensure consistency across platforms.
内容的提问来源于stack exchange,提问作者Vishal Avalani




