flutter状态库到底用哪个!

初入flutter,状态库到底用哪个!

我之前没写过flutter,进了新公司要求使用flutter。由于对flutetr的生态不熟悉,常常感到疑惑,我到底该用哪个库。其中最有争议的就是状态库的选择,在查找了诸多资料和群里询问交流后,写下了这篇文章,对我所接触到的一些flutter库进行调研。

状态库到底用getx还是provider,令刚开始接触flutter的我甚为纠结,provider是官方推荐,但感觉不是很好用,getx网友褒贬不一,还说什么初学者用了getx就废了(笑哭),让我不知该如何是好,因为我都不熟悉,后来又了解到provider的作者还写了一个更好用的库riverpod,去了解了一下,然后选择就变成了getx or riverpod。

由于一时之间不能决定,于是我决定分开两个分支来写,一个使用riverpod + flutter_hooks,一个用getx。

使用riverpod + flutter_hooks

需要安装的依赖:flutter pub add hooks_riverpod flutter_hooks 这个写法使用的hook对于react和vue3开发者非常的友好,符合使用习惯,最重要的是很方便。

写法如下:

使用HookConsumerWidget,定义一个响应式更新到变量使用useState: final isButtonDisabled = useState<bool>(false);,这样就可以愉快的使用定义的变量了,页面会响应式更新的。

dart 复制代码
class EmailVerificationScreen extends HookConsumerWidget {
  final Map<String, dynamic>? arguments;

  const EmailVerificationScreen(this.arguments, {super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final formKey = useMemoized(() => GlobalKey<FormState>());
    final emailCodeController = useTextEditingController();

    final isButtonDisabled = useState<bool>(false);

    Future<void> nextStep(BuildContext context) async {
      try {
        SRouter(context).pushNamed("SetPassword", queryParameters: {
          "account": arguments?['account'],
        });
      } catch (e) {
        print(e);
      }
    }

    useEffect(() {
      listener() {
        isButtonDisabled.value = emailCodeController.text.length < 6;
      }

      emailCodeController.addListener(listener);
      return () {
        emailCodeController.removeListener(listener);
      };
    }, [emailCodeController]);

    return Form(
      key: formKey,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            S.of(context).emailVerification,
            style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.w400),
          ),
          SizedBox(height: 8.w),
          Text(
            S.current
                .enterThe6DigitVerificationCodeReceivedInYourEmailTheCodeIsValidFor30Minutes,
            style: TextStyle(
              color: const Color.fromRGBO(143, 143, 143, 1),
              fontSize: 12.sp,
              fontWeight: FontWeight.w400,
            ),
          ),
          SizedBox(height: 39.w),
          SStyledInput(
            controller: emailCodeController,
            labelText: S.of(context).emailVerificationCode,
            validator: (value) => Validators.validateEmail(value, context),
          ),
          SizedBox(height: 10.w),
          SizedBox(
            height: 50.w,
            width: double.infinity,
            child: ElevatedButton(
              onPressed:
                  isButtonDisabled.value ? null : () => nextStep(context),
              child: Text(S.of(context).nextStep),
            ),
          ),
          SizedBox(
            height: 26.w,
          ),
          GestureDetector(
            onTap: () {
              SRouter(context).pushNamed("Register");
            },
            child: Text(
              S.of(context).didNotReceiveTheCode,
              style: const TextStyle(
                color: Color.fromRGBO(79, 160, 255, 1.0),
                fontSize: 12,
              ),
            ),
          )
        ],
      ),
    );
  }
}
  • 全局状态管理:

大体如下,使用一个全局的NotifierProvider

scala 复制代码
final userProvider = NotifierProvider<UserProvide, UserState>(UserProvide.new);

class UserState {
  /// 当前活跃用户
  User? activeUser;

  /// 多用户会话管理
  List<User> userList = [];

  String? get getToken {
    return activeUser?.accessToken;
  }
}

class UserProvide extends Notifier<UserState> {
  @override
  UserState build() => UserState();

  /// 登录
  Future<void> login(User user) async {
    final existingUser =
        state.userList.firstWhereOrNull((u) => u.userId == user.userId);

getx

getx是使用的false.obs的形式

dart 复制代码
var isButtonDisabled = false.obs;

路由对比

由于getx还有路由的相关功能,所以这里再加一个getx 路由和go_router的对比,go_router是官方推荐的。

getx router

  • router.dart getx路由提供了一些转场动画,挺方便的
php 复制代码
const Transition transition = Transition.rightToLeft;

List<GetPage> routes = [
  GetPage(
      name: '/',
      page: () => const HomeScreen(username: ''),
      transition: transition),
  GetPage(
      name: '/login',
      page: () => const DefaultLayout(
            title: LocaleKeys.login,
            child: LoginScreen(),
          ),
      transition: transition),
  GetPage(
      name: '/welcomeBack',
      page: () => DefaultLayout(
            title: LocaleKeys.welcomeBack,
            child: WelcomeBackScreen(),
          ),
      transition: transition),
  GetPage(
      name: '/notFound',
      page: () => const NotFoundScreen(),
      transition: Transition.fade),
];
  • 跳转:

可以看到getx并没有依赖context

dart 复制代码
Get.toNamed(
  '/welcomeBack',
  arguments: {'email': email},
);

go_router

这里要吐槽一句go_router的官方文档容易让人误解。上来就让人context.go('/users/123'),也没说清楚一点,这个操作数据是不进入路由栈的。所以路由回退不了,让我疑惑半天,差点就换库了,和群友交流了一下才晓得,不少哥们都被坑过。

  • index.dart
php 复制代码
final goRouter = GoRouter(
  initialLocation: FlutterConfig.initialLocation,
  redirect: routeBeforeHook,
  // debugLogDiagnostics: true,
  routes: baseRoutes + whiteList,
);
  • routes.dart
php 复制代码
List<GoRoute> baseRoutes = [  GoRoute(
  name: "Home",
  path: '/',
  builder: (context, state) => const HomeScreen(),
),];
  • 跳转:
ini 复制代码
context.pushNamed(name);
  • 这里需要说一下的就是go_router配合riverpod的使用,如何在go_router里面拿到riverpod的状态:
javascript 复制代码
String? routeBeforeHook(BuildContext context, GoRouterState state) {
  final userState = ProviderScope.containerOf(context).read(userProvider);

  if (userState.getToken == null &&
      !whiteList.map((e) => e.path).contains(state.uri.path)) {
    return '/login';
  } else {
    return null;
  }
}

国际化对比

getx

如果使用getx的国际化功能,建议配合getx_cli使用,因为这个可以根据json文件生成,json是通用文件,可以方便替换和多项目使用,而且生成之后就有提示了。

dart 复制代码
import 'package:get/get.dart';

class Messages extends Translations {
  @override
  Map<String, Map<String, String>> get keys => {
        'zh_CN': {
          'hello': '你好 世界',
        },
        'de_DE': {
          'hello': 'Hallo Welt',
        }
      };
}
  • 使用:

这个使用方式虽然也方便,但是没有提示

bash 复制代码
Text('title'.tr);

flutter_localizations

这是官方推荐的,最好配合编辑器插件Flutter Intl使用。在lib目录下创建I10n文件夹,使用的是.arb后缀的文件,这个文件的格式和json一模一样,插件就会自动帮助生成相应代码了。

总结

总结下来,于我来说大概就是两条路线,一条是getx全家桶一把梭,一条就是hooks_riverpod + fluter_hooks + go_route + flutter_localizations。

接下来还要写react的项目,我对react的生态也不太熟,还要去继续去调研一下react的状态库以及相关的生态。

相关推荐
Nan_Shu_61417 分钟前
学习: Threejs (2)
前端·javascript·学习
G_G#25 分钟前
纯前端js插件实现同一浏览器控制只允许打开一个标签,处理session变更问题
前端·javascript·浏览器标签页通信·只允许一个标签页
@大迁世界41 分钟前
TypeScript 的本质并非类型,而是信任
开发语言·前端·javascript·typescript·ecmascript
GIS之路1 小时前
GDAL 实现矢量裁剪
前端·python·信息可视化
是一个Bug1 小时前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu121381 小时前
React面向组件编程
开发语言·前端·javascript
持续升级打怪中1 小时前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化
GIS之路1 小时前
GDAL 实现矢量合并
前端
hxjhnct1 小时前
React useContext的缺陷
前端·react.js·前端框架
前端 贾公子2 小时前
从入门到实践:前端 Monorepo 工程化实战(4)
前端