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的状态库以及相关的生态。

相关推荐
什么鬼昵称16 分钟前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色33 分钟前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2341 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河1 小时前
CSS总结
前端·css
BigYe程普1 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
余生H2 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
程序员-珍2 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
axihaihai2 小时前
网站开发的发展(后端路由/前后端分离/前端路由)
前端
流烟默2 小时前
Vue中watch监听属性的一些应用总结
前端·javascript·vue.js·watch
2401_857297912 小时前
招联金融2025校招内推
java·前端·算法·金融·求职招聘