每天一个高级前端知识 - Day 21

每天一个高级前端知识 - Day 21

今日主题:跨端开发 - React Native 与 Flutter 的深度对比与实践

核心概念:跨端不是"Write Once, Run Anywhere",而是"Learn Once, Write Anywhere"

跨端技术的本质是在开发效率性能体验平台一致性之间寻找最佳平衡点。

🔬 技术架构对比

scss 复制代码
┌─────────────────────────────────────────────────────────────┐
│                      React Native                            │
├─────────────────────────────────────────────────────────────┤
│  JavaScript/TS 代码                                          │
│         ↓ (JS引擎: Hermes/JSC)                               │
│  Bridge / JSI (新架构)                                       │
│         ↓ (序列化/直接调用)                                   │
│  原生组件 (UIKit/Android View)                               │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                         Flutter                              │
├─────────────────────────────────────────────────────────────┤
│  Dart 代码                                                   │
│         ↓ (AOT编译/JIT)                                      │
│  Flutter Framework (Widget/Render)                          │
│         ↓                                                   │
│  Skia 引擎 (自绘)                                            │
│         ↓                                                   │
│  Canvas (无原生组件)                                          │
└─────────────────────────────────────────────────────────────┘

⚛️ React Native 深度实践

jsx 复制代码
// ============ React Native 项目结构 ============
// App.tsx
import React, { useState, useEffect } from 'react';
import {
  SafeAreaView,
  StyleSheet,
  Text,
  View,
  FlatList,
  TouchableOpacity,
  Animated,
  Platform,
  StatusBar
} from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

// 平台特定代码
const isIOS = Platform.OS === 'ios';
const statusBarHeight = Platform.OS === 'ios' ? 44 : StatusBar.currentHeight;

// 自定义Hooks
const useAnimations = () => {
  const fadeAnim = new Animated.Value(0);
  const scaleAnim = new Animated.Value(0.9);
  
  const startAnimation = () => {
    Animated.parallel([
      Animated.timing(fadeAnim, {
        toValue: 1,
        duration: 500,
        useNativeDriver: true
      }),
      Animated.spring(scaleAnim, {
        toValue: 1,
        tension: 50,
        friction: 7,
        useNativeDriver: true
      })
    ]).start();
  };
  
  return { fadeAnim, scaleAnim, startAnimation };
};

// 高性能列表组件
const ProductList = ({ data, onEndReached }) => {
  const renderItem = ({ item, index }) => (
    <Animated.View style={[styles.itemContainer, {
      transform: [{ scale: 0.95 }]
    }]}>
      <TouchableOpacity
        activeOpacity={0.7}
        onPress={() => console.log('Pressed', item.id)}
      >
        <View style={styles.card}>
          <Text style={styles.title}>{item.name}</Text>
          <Text style={styles.price}>¥{item.price}</Text>
        </View>
      </TouchableOpacity>
    </Animated.View>
  );
  
  return (
    <FlatList
      data={data}
      renderItem={renderItem}
      keyExtractor={item => item.id}
      onEndReached={onEndReached}
      onEndReachedThreshold={0.5}
      initialNumToRender={10}
      maxToRenderPerBatch={5}
      windowSize={5}
      removeClippedSubviews={true}
    />
  );
};

// 自定义性能优化组件
const OptimizedImage = React.memo(({ uri, style }) => {
  const [isLoaded, setIsLoaded] = useState(false);
  
  return (
    <View style={style}>
      {!isLoaded && <View style={styles.imagePlaceholder} />}
      <Image
        source={{ uri }}
        style={[style, { opacity: isLoaded ? 1 : 0 }]}
        onLoad={() => setIsLoaded(true)}
        progressiveRenderingEnabled
        fadeDuration={300}
      />
    </View>
  );
});

// 原生模块桥接(iOS/Android原生代码调用)
import { NativeModules, NativeEventEmitter } from 'react-native';

const { CameraModule, BiometricModule } = NativeModules;

const useBiometricAuth = () => {
  const authenticate = async () => {
    try {
      const result = await BiometricModule.authenticate();
      return result.success;
    } catch (error) {
      console.error('生物识别失败:', error);
      return false;
    }
  };
  
  return { authenticate };
};

// 主应用组件
const App = () => {
  const [products, setProducts] = useState([]);
  const [page, setPage] = useState(1);
  const { fadeAnim, scaleAnim, startAnimation } = useAnimations();
  const { authenticate } = useBiometricAuth();
  
  useEffect(() => {
    startAnimation();
    loadProducts();
  }, []);
  
  const loadProducts = async () => {
    const response = await fetch(`/api/products?page=${page}`);
    const newProducts = await response.json();
    setProducts(prev => [...prev, ...newProducts]);
    setPage(prev => prev + 1);
  };
  
  return (
    <SafeAreaView style={styles.container}>
      <StatusBar barStyle="dark-content" backgroundColor="#fff" />
      <Animated.View style={[styles.content, {
        opacity: fadeAnim,
        transform: [{ scale: scaleAnim }]
      }]}>
        <Text style={styles.header}>商品列表</Text>
        <ProductList data={products} onEndReached={loadProducts} />
      </Animated.View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5'
  },
  content: {
    flex: 1,
    paddingTop: statusBarHeight
  },
  header: {
    fontSize: 24,
    fontWeight: 'bold',
    padding: 16,
    color: '#333'
  },
  card: {
    backgroundColor: '#fff',
    borderRadius: 8,
    padding: 16,
    marginHorizontal: 16,
    marginVertical: 8,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3
  },
  title: {
    fontSize: 18,
    fontWeight: '500'
  },
  price: {
    fontSize: 16,
    color: '#ff6b6b',
    marginTop: 8
  }
});

🎨 Flutter 深度实践

dart 复制代码
// ============ Flutter 项目 ============
// main.dart
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const ProductListPage(),
    );
  }
}

// 商品模型
class Product {
  final String id;
  final String name;
  final double price;
  final String imageUrl;
  
  Product({
    required this.id,
    required this.name,
    required this.price,
    required this.imageUrl,
  });
  
  factory Product.fromJson(Map<String, dynamic> json) {
    return Product(
      id: json['id'],
      name: json['name'],
      price: json['price'],
      imageUrl: json['imageUrl'],
    );
  }
}

// 状态管理(Riverpod)
final productProvider = FutureProvider<List<Product>>((ref) async {
  final response = await http.get(Uri.parse('/api/products'));
  final List<dynamic> data = json.decode(response.body);
  return data.map((json) => Product.fromJson(json)).toList();
});

final cartProvider = StateNotifierProvider<CartNotifier, List<Product>>((ref) {
  return CartNotifier();
});

class CartNotifier extends StateNotifier<List<Product>> {
  CartNotifier() : super([]);
  
  void addToCart(Product product) {
    state = [...state, product];
  }
  
  void removeFromCart(Product product) {
    state = state.where((p) => p.id != product.id).toList();
  }
  
  double get totalPrice => state.fold(0, (sum, item) => sum + item.price);
}

// 自定义动画组件
class ShimmerEffect extends StatefulWidget {
  final Widget child;
  final Duration duration;
  
  const ShimmerEffect({
    super.key,
    required this.child,
    this.duration = const Duration(milliseconds: 1500),
  });
  
  @override
  State<ShimmerEffect> createState() => _ShimmerEffectState();
}

class _ShimmerEffectState extends State<ShimmerEffect>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<Alignment> _alignmentAnimation;
  
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: widget.duration);
    _alignmentAnimation = AlignmentTween(
      begin: Alignment(-1.2, 0),
      end: Alignment(1.2, 0),
    ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
    
    _controller.repeat();
  }
  
  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return ShaderMask(
          shaderCallback: (bounds) {
            return LinearGradient(
              begin: _alignmentAnimation.value,
              end: _alignmentAnimation.value + const Alignment(0.5, 0),
              colors: const [
                Colors.transparent,
                Colors.white70,
                Colors.transparent,
              ],
              stops: const [0.0, 0.5, 1.0],
            ).createShader(bounds);
          },
          blendMode: BlendMode.srcATop,
          child: child,
        );
      },
      child: widget.child,
    );
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

// 高性能商品列表
class ProductListPage extends ConsumerStatefulWidget {
  const ProductListPage({super.key});
  
  @override
  ConsumerState<ProductListPage> createState() => _ProductListPageState();
}

class _ProductListPageState extends ConsumerState<ProductListPage>
    with AutomaticKeepAliveClientMixin {
  final ScrollController _scrollController = ScrollController();
  bool _isLoadingMore = false;
  
  @override
  bool get wantKeepAlive => true;
  
  @override
  void initState() {
    super.initState();
    _scrollController.addListener(_onScroll);
  }
  
  void _onScroll() {
    if (_scrollController.position.pixels >=
        _scrollController.position.maxScrollExtent - 200) {
      _loadMore();
    }
  }
  
  Future<void> _loadMore() async {
    if (_isLoadingMore) return;
    setState(() => _isLoadingMore = true);
    
    // 加载更多数据
    await Future.delayed(const Duration(seconds: 1));
    
    setState(() => _isLoadingMore = false);
  }
  
  @override
  Widget build(BuildContext context) {
    super.build(context);
    
    final productState = ref.watch(productProvider);
    final cart = ref.watch(cartProvider);
    
    return Scaffold(
      appBar: AppBar(
        title: const Text('商品列表'),
        actions: [
          Stack(
            children: [
              IconButton(
                icon: const Icon(Icons.shopping_cart),
                onPressed: () {
                  // 跳转到购物车
                },
              ),
              if (cart.isNotEmpty)
                Positioned(
                  right: 8,
                  top: 8,
                  child: Container(
                    padding: const EdgeInsets.all(2),
                    decoration: const BoxDecoration(
                      color: Colors.red,
                      shape: BoxShape.circle,
                    ),
                    constraints: const BoxConstraints(
                      minWidth: 16,
                      minHeight: 16,
                    ),
                    child: Text(
                      cart.length.toString(),
                      style: const TextStyle(
                        color: Colors.white,
                        fontSize: 10,
                      ),
                      textAlign: TextAlign.center,
                    ),
                  ),
                ),
            ],
          ),
        ],
      ),
      body: productState.when(
        data: (products) {
          return CustomScrollView(
            controller: _scrollController,
            slivers: [
              SliverList(
                delegate: SliverChildBuilderDelegate(
                  (context, index) {
                    final product = products[index];
                    return ProductCard(product: product);
                  },
                  childCount: products.length,
                ),
              ),
              if (_isLoadingMore)
                const SliverToBoxAdapter(
                  child: Center(
                    child: Padding(
                      padding: EdgeInsets.all(16),
                      child: CircularProgressIndicator(),
                    ),
                  ),
                ),
            ],
          );
        },
        loading: () => const Center(child: CircularProgressIndicator()),
        error: (error, stack) => Center(child: Text('Error: $error')),
      ),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: () {},
        icon: const Icon(Icons.payment),
        label: Text('总计 ¥${ref.watch(cartProvider.notifier).totalPrice}'),
      ),
    );
  }
  
  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }
}

// 商品卡片组件(高性能)
class ProductCard extends StatelessWidget {
  final Product product;
  
  const ProductCard({super.key, required this.product});
  
  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.all(8),
      elevation: 2,
      child: InkWell(
        onTap: () {
          // 跳转到详情页
        },
        child: Padding(
          padding: const EdgeInsets.all(12),
          child: Row(
            children: [
              ClipRRect(
                borderRadius: BorderRadius.circular(8),
                child: Image.network(
                  product.imageUrl,
                  width: 80,
                  height: 80,
                  fit: BoxFit.cover,
                  loadingBuilder: (context, child, loadingProgress) {
                    if (loadingProgress == null) return child;
                    return Container(
                      width: 80,
                      height: 80,
                      color: Colors.grey[200],
                      child: const Center(
                        child: CircularProgressIndicator(),
                      ),
                    );
                  },
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      product.name,
                      style: const TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      '¥${product.price.toStringAsFixed(2)}',
                      style: const TextStyle(
                        fontSize: 18,
                        color: Colors.red,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ],
                ),
              ),
              Consumer(
                builder: (context, ref, child) {
                  return IconButton(
                    icon: const Icon(Icons.add_shopping_cart),
                    onPressed: () {
                      ref.read(cartProvider.notifier).addToCart(product);
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(content: Text('已添加 ${product.name}')),
                      );
                    },
                  );
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

// 自定义绘画组件(Canvas)
class CustomPainterWidget extends StatelessWidget {
  const CustomPainterWidget({super.key});
  
  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      size: Size.infinite,
      painter: BackgroundPainter(),
      child: Container(),
    );
  }
}

class BackgroundPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.blue.withOpacity(0.1)
      ..style = PaintingStyle.fill;
    
    final path = Path();
    path.moveTo(0, size.height * 0.8);
    path.quadraticBezierTo(
      size.width / 2,
      size.height,
      size.width,
      size.height * 0.7,
    );
    path.lineTo(size.width, size.height);
    path.lineTo(0, size.height);
    path.close();
    
    canvas.drawPath(path, paint);
  }
  
  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

📊 性能对比与最佳实践

javascript 复制代码
// React Native 性能优化清单
const RNOptimizations = {
  // 1. 使用 Hermes 引擎
  // 在 metro.config.js 中配置
  
  // 2. FlatList 优化
  optimizedList: {
    initialNumToRender: 10,
    maxToRenderPerBatch: 5,
    windowSize: 5,
    removeClippedSubviews: true,
    getItemLayout: (data, index) => ({
      length: 100,
      offset: 100 * index,
      index
    })
  },
  
  // 3. 使用 InteractionManager
  scheduleTask: () => {
    InteractionManager.runAfterInteractions(() => {
      // 执行非关键任务
    });
  },
  
  // 4. 图片优化
  imageOptimization: {
    useFastImage: true,
    cacheControl: 'cache',
    priority: 'high'
  },
  
  // 5. 避免内联函数
  avoidInlineFunctions: () => {
    // ❌ 不好
    // <TouchableOpacity onPress={() => handlePress(item)} />
    
    // ✅ 好
    // const handlePress = useCallback(() => handlePress(item), [item]);
    // <TouchableOpacity onPress={handlePress} />
  }
};

// Flutter 性能优化清单
const FlutterOptimizations = {
  // 1. 使用 const 构造函数
  // 2. 使用 ListView.builder (类似 FlatList)
  // 3. 使用 RepaintBoundary 隔离重绘区域
  // 4. 使用 Keys 保持状态
  // 5. 异步操作使用 compute (类似 Web Worker)
};

🎯 今日挑战

实现一个跨端图片编辑器,要求:

  1. 同时支持 React Native 和 Flutter 双端
  2. 实现图片裁剪、滤镜、文字添加功能
  3. 高性能处理大图(使用原生模块)
  4. 实现撤销/重做功能
  5. 支持导出到相册
  6. 代码复用策略(React Native 使用 shared 目录,Flutter 使用统一逻辑层)

明日预告:前端未来趋势 - 2026年的前端技术展望(AI、边缘计算、新标准)

💡 跨端箴言 :"真正的跨端不是代码复用,而是知识复用"------理解不同平台的差异,才能写出最佳实践!

相关推荐
暗不需求1 小时前
前端性能优化 防抖与节流完全指南:从原理到最佳实践
前端·javascript·面试
@大迁世界1 小时前
45.什么是内联条件表达式(inline conditional expressions)?在事件处理里怎么用?
开发语言·前端·javascript·react.js·ecmascript
一颗趴菜1 小时前
微信小程序如何去下载PDF呢
前端·javascript
KaMeidebaby1 小时前
卡梅德生物技术快报|细菌 FISH 实验 + 流式细胞术:尿路感染活菌快速定量系统实现与数据验证
前端·数据库·其他·百度·新浪微博
昆曲之源_娄江河畔2 小时前
DBGridEh Footer的使用
前端·数据库·delphi·dbgrideh
廖松洋(Alina)2 小时前
02数据模型与单词仓库-鸿蒙PC端Electron开发
前端·华为·electron·开源·harmonyos·鸿蒙
幽络源小助理2 小时前
最新短网址系统源码 分用户链接 - 幽络源免费源码分享
前端·php
Muen2 小时前
SwiftUI-学习路线
前端
小小小小宇2 小时前
普通 H5 新版本部署后通知用户更新方案
前端