You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中如何使用HookWidget与useTextEditingController实现登录表单功能

实现方案优化

效果演示

疑问解答

  • useTextEditingController会自动完成销毁逻辑,所有flutter_hooks提供的内置Hook都封装了生命周期管理,不需要手动调用dispose。
  • 所有use开头的Hook方法必须写在build方法内部,不能作为类的属性初始化,你的现有写法是错误的,类属性只会在实例化时执行一次,会触发Hook使用规则异常。

Flutter Hooks 正确实现

无需手动添加监听、无需调用setState,通过useListenable监听输入框变化触发局部重绘,性能优于全页面刷新的StatefulWidget方案:

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Login Demo',
      home: Scaffold(
        body: Center(
          child: SizedBox(
            width: 400,
            child: MyLoginPage2(),
          ),
        ),
      ),
    );
  }
}

class MyLoginPage2 extends HookWidget {
  MyLoginPage2({super.key});

  @override
  Widget build(BuildContext context) {
    // Hook必须在build内部初始化
    final emailController = useTextEditingController(text: '');
    final passwordController = useTextEditingController(text: '');
    // 监听两个输入框的状态变化
    useListenable(emailController);
    useListenable(passwordController);

    final isLoginEnabled = emailController.text.isNotEmpty && passwordController.text.isNotEmpty;

    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        TextField(
          controller: emailController,
          keyboardType: TextInputType.emailAddress,
          decoration: const InputDecoration(hintText: '邮箱'),
        ),
        TextField(
          controller: passwordController,
          obscureText: true,
          decoration: const InputDecoration(hintText: '密码'),
        ),
        const SizedBox(height: 20),
        ElevatedButton(
          onPressed: isLoginEnabled ? () => print("登录逻辑执行") : null,
          child: Text(isLoginEnabled ? '登录可用' : '登录禁用'),
        )
      ],
    );
  }
}

Riverpod 实现方案(更适合复杂表单场景)

状态完全脱离Widget生命周期管理,后续扩展表单校验、状态共享等需求更灵活:

import 'package:flutter/material.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: 'Login Demo',
      home: Scaffold(
        body: Center(
          child: SizedBox(
            width: 400,
            child: MyLoginPage(),
          ),
        ),
      ),
    );
  }
}

// 定义表单状态Provider
final emailProvider = StateProvider<String>((ref) => '');
final passwordProvider = StateProvider<String>((ref) => '');
// 派生登录按钮可用状态
final isLoginEnabledProvider = Provider<bool>((ref) {
  final email = ref.watch(emailProvider);
  final password = ref.watch(passwordProvider);
  return email.isNotEmpty && password.isNotEmpty;
});

class MyLoginPage extends ConsumerWidget {
  const MyLoginPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        TextField(
          keyboardType: TextInputType.emailAddress,
          decoration: const InputDecoration(hintText: '邮箱'),
          onChanged: (value) => ref.read(emailProvider.notifier).state = value,
        ),
        TextField(
          obscureText: true,
          decoration: const InputDecoration(hintText: '密码'),
          onChanged: (value) => ref.read(passwordProvider.notifier).state = value,
        ),
        const SizedBox(height: 20),
        ElevatedButton(
          onPressed: ref.watch(isLoginEnabledProvider) ? () => print("登录逻辑执行") : null,
          child: Text(ref.watch(isLoginEnabledProvider) ? '登录可用' : '登录禁用'),
        )
      ],
    );
  }
}

内容的提问来源于stack exchange,提问作者Robert Estivill

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 18:24:05