Flutter学习 - 组件通信与网络请求Dio

目录

组件通信

[组件通信 - 父传子(构造函数传参数)](#组件通信 - 父传子(构造函数传参数))

步骤:

小案例:

[组件通信 - 子传父(回调函数)](#组件通信 - 子传父(回调函数))

[网络请求 - Dio插件使用](#网络请求 - Dio插件使用)

[网络请求案例 - 1. 封装Dio工具](#网络请求案例 - 1. 封装Dio工具)

[网络请求案例 - 2. 初始化获取数据](#网络请求案例 - 2. 初始化获取数据)

[网络请求案例 - 3. 解决web端跨域问题](#网络请求案例 - 3. 解决web端跨域问题)

[网络请求案例 - 4. 父传子实现](#网络请求案例 - 4. 父传子实现)

路由管理:


组件通信

组件通信 - 父传子(构造函数传参数)

|-----------------|-----------|----------|
| 通信方式 | 方向 | 适用场景 |
| 构造函数传递 | 父 => 子 | 简单的数据传递 |
| 回调函数 | 子 => 父 | 子组件通知父组件 |
| InheritedWidget | 祖先 => 后代 | 跨层级数据共享 |
| Provider | 任意组件间 | 状态管理推荐方案 |
| EventBus | 任意组件间 | 全局事件通信 |
| Bloc/Riverpod | 任意组件间 | 复杂状态管理 |

步骤:
  1. 子组件定义接收属性
  2. 子组件在构造函数中接收参数
  3. 父组件传递属性给子组件
  4. 有状态组件在'对外的类'接受属性,'对内的类'通过widget对象获取对应属性

子组件定义接收属性需要使用final关键字 - 因为属性由父组件决定,子组件不能随意更改

子组件属性如果没有初始值,需要在构造函数中使用required来接收属性

Dart 复制代码
import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(MainPage());
}

// 父组件
class MainPage extends StatelessWidget {
  const MainPage({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Container(
          alignment: Alignment.center,
          child: Column(
            children: [
              Text("父组件", style: TextStyle(color: Colors.blue, fontSize: 30)),
              Child(message: "张三"),
              ChildActive(message: "李四")
            ],
          ),
        ),
      ),
    );
  }
}

// 无状态组件的子组件
class Child extends StatelessWidget {
 final  String? message; // 子组件定义属性(使用final)
 // 构造函数中接收参数
  const Child({super.key,this.message});

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text("无状态子组件 - ${message}", style: TextStyle(color: Colors.red, fontSize: 18)),
    );
  }
}

// 有状态组件的子组件
class ChildActive extends StatefulWidget { // 对外接收
  final String message; // 用final定义
  const ChildActive({super.key,required this.message});

  @override
  State<ChildActive> createState() => _ChildActiveState();
}

class _ChildActiveState extends State<ChildActive> { // 对内接收
  @override
  Widget build(BuildContext context) {
    return Container(
      // 对内使用widget对象接收
      child: Text("有状态的子组件 -- ${widget.message}", style: TextStyle(color: Colors.red, fontSize: 18)),
    );
  }
}
小案例:
Dart 复制代码
import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(MainPage());
}

// 父组件
class MainPage extends StatefulWidget {
  const MainPage({super.key});

  @override
  State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  List<String> _list = ['鱼香肉丝', '宫保鸡丁', '麻婆豆腐', '酸辣土豆丝', '酱排骨'];

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: GridView.count(
            padding: EdgeInsets.all(10),
            crossAxisCount: 2,
            mainAxisSpacing: 10,
            crossAxisSpacing: 10,
            children: List.generate(_list.length, (index) {
              return Child(foodName: _list[index],); // 返回整个子组件
            })),
      ),
    );
  }
}

// 子组件
class Child extends StatefulWidget {
  final String foodName;
  const Child({super.key,required this.foodName});

  @override
  State<Child> createState() => _ChildState();
}

class _ChildState extends State<Child> {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue,
      alignment: Alignment.center,
      child: Text(widget.foodName,style: TextStyle(color:Colors.white,fontSize: 20),),
    );
  }
}
组件通信 - 子传父(回调函数)

步骤:

  1. 父组件传一个函数给子组件

  2. 子组件调用该函数

  3. 父组件通过回调函数获取参数

需求:

点击子组件删除父组件的菜品数据并更新列表

Dart 复制代码
import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(MainPage());
}

// 父组件
class MainPage extends StatefulWidget {
  const MainPage({super.key});

  @override
  State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  List<String> _list = ['鱼香肉丝', '宫保鸡丁', '麻婆豆腐', '酸辣土豆丝', '酱排骨'];

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: GridView.count(
            padding: EdgeInsets.all(10),
            crossAxisCount: 2,
            mainAxisSpacing: 10,
            crossAxisSpacing: 10,
            children: List.generate(_list.length, (index) {
              return Child(foodName: _list[index],index: index, delFood: (int index) {
                print("父接收$index");
                _list.removeAt(index);
                setState(() {});
              }); // 返回整个子组件
            })),
      ),
    );
  }
}

// 子组件
class Child extends StatefulWidget {
  final String foodName;
  final int index;
  final Function(int index) delFood; // 声明函数属性
  const Child({super.key,required this.foodName,required this.index,required this.delFood});

  @override
  State<Child> createState() => _ChildState();
}

class _ChildState extends State<Child> {
  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: Alignment.topRight,
      children: [
        Container(
            color: Colors.blue,
            alignment: Alignment.center,
            child: Text(widget.foodName,style: TextStyle(color:Colors.white,fontSize: 20),),
        ),
        IconButton(color:Colors.red,onPressed: (){
          print("删除菜单${widget.index}");
          widget.delFood(widget.index);
        },icon: Icon(Icons.delete))
      ],
    );
  }
}

网络请求 - Dio插件使用

安装dio:flutter pub add dio

基本使用:

Dio( ).get( 地址 ).then( ).catchError( )

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

void main(List<String> args) {
  Dio().get("https://geek.itheima.net/v1_0/channels").then((res) {
    print(res);
  }).catchError((error) {});
}

一般情况下,在初始化状态initState获取页面数据

网络请求案例 - 1. 封装Dio工具
  1. 创建工具类
  2. 构造函数2中设置基础地址和超时时间
  3. 添加各类拦截器
  4. 封装统一请求方法
  5. 请求频道数据进行循环渲染解决web端跨域问题
  6. 实现UI渲染绘制
Dart 复制代码
import 'package:dio/dio.dart';

void main(List<String> args) {

}

// 封装一个工具类
class DioUtils {
  final Dio _dio = Dio(); // 内部Dio对象
  DioUtils(){
    // 基本操作
    // 配置基础地址和超时时间
    // _dio.options.baseUrl =  "https://geek.itheima.net/v1_0/";
    // _dio.options.connectTimeout = Duration(seconds: 10); // 连接超时
    // _dio.options.sendTimeout = Duration(seconds: 10); // 发送超时
    // _dio.options.receiveTimeout = Duration(seconds: 10); // 接收超时
    // 简 写 ..连续赋值的写法
    _dio.options..baseUrl =  "https://geek.itheima.net/v1_0/"
        ..connectTimeout = Duration(seconds: 10)
        ..sendTimeout = Duration(seconds: 10)
        ..receiveTimeout = Duration(seconds: 10);

    // 拦截器
    _addInterceptor(); // 添加注册拦截器
  }
  void _addInterceptor() {
    _dio.interceptors.add(InterceptorsWrapper(
      // 请求拦截器
      onRequest: (context,handler) {
        // handler.next(requestOptions) // 放过请求
        // handler.reject(error)  拦截请求
        handler.next(context);
      },
     // 响应拦截器
      onResponse: (context,handler){
        // handler.next(response);
        // http 状态码 2xx 成功 3xx 4xx 5xx
        if(context.statusCode! >= 200 && context.statusCode! < 300){
          handler.next(context); //放过
          return;
        }
        // 说明异常
        handler.reject(DioException(requestOptions: context.requestOptions));
      },
      //   错误拦截器
      onError: (context,handler){
        handler.reject(context); // 直接抛出异常
      }
    ));
  }
  // 向外暴露一个get方法
  get(String url,{Map<String,dynamic>? params}){
    return _dio.get(url,queryParameters: params);
  }
}
网络请求案例 - 2. 初始化获取数据
网络请求案例 - 3. 解决web端跨域问题

默认情况下,flutter运行web端加载网络资源会报跨域提示错误

网络请求案例 - 4. 父传子实现
Dart 复制代码
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(MainPage());
}
class MainPage extends StatefulWidget {
  const MainPage({super.key});

  @override
  State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    // 发起网络请求
    __getChannels();
  }
  List<Map<String,dynamic>> _list = []; // 用来接收数据

  void __getChannels() async{
    DioUtils dioUtil = DioUtils();
    Response<dynamic> result =  await dioUtil.get("channels");
    Map<String,dynamic> res = result.data as Map<String,dynamic>;
    List data = res['data']['channels'] as List;
    // cast 强制转换列表项的类型
   _list = data.cast<Map<String,dynamic>>() as List<Map<String,dynamic>>;
   setState(() {}); // 执行方法,UI才会更新
   // channels是一个后端支持前端跨域访问的接口

  }
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar:AppBar(title: Text("频道管理")),
        body: GridView.extent(maxCrossAxisExtent: 140,
        padding: EdgeInsets.all(10),
        mainAxisSpacing: 10,
        crossAxisSpacing: 10,
        childAspectRatio: 3,
        children: List.generate(_list.length, (index){
          return ChannelItem(item:_list[index],index: index,delItem: (int index){
            _list.removeAt(index);
            setState(() {});
          });
        }),
        )
      ),
    );
  }
}

// 用来绘制每个频道的UI内容
class ChannelItem extends StatelessWidget {
  final Map<String,dynamic> item;
  final int index;
  final Function(int index) delItem;
  const ChannelItem({super.key,required this.item,required this.index, required this.delItem});

  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: Alignment.topRight,
      children: [
          Container(
          color: Colors.blue,
          alignment: Alignment.center,
          child: Text(
            item['name'] ?? "空",
            style: TextStyle(color:Colors.white,fontSize: 14),
          ),
        ),
        IconButton(icon: Icon(Icons.delete),color: Colors.red,onPressed: (){
          delItem(index);
        })
      ],
    );
  }
}

// 封装一个工具类
class DioUtils {
  final Dio _dio = Dio(); // 内部Dio对象
  DioUtils(){
    // 基本操作
    // 配置基础地址和超时时间
    // _dio.options.baseUrl =  "https://geek.itheima.net/v1_0/";
    // _dio.options.connectTimeout = Duration(seconds: 10); // 连接超时
    // _dio.options.sendTimeout = Duration(seconds: 10); // 发送超时
    // _dio.options.receiveTimeout = Duration(seconds: 10); // 接收超时
    // 简 写 ..连续赋值的写法
    _dio.options..baseUrl =  "https://geek.itheima.net/v1_0/"
        ..connectTimeout = Duration(seconds: 10)
        ..sendTimeout = Duration(seconds: 10)
        ..receiveTimeout = Duration(seconds: 10);

    // 拦截器
    _addInterceptor(); // 添加注册拦截器
  }
  void _addInterceptor() {
    _dio.interceptors.add(InterceptorsWrapper(
      // 请求拦截器
      onRequest: (context,handler) {
        // handler.next(requestOptions) // 放过请求
        // handler.reject(error)  拦截请求
        handler.next(context);
      },
     // 响应拦截器
      onResponse: (context,handler){
        // handler.next(response);
        // http 状态码 2xx 成功 3xx 4xx 5xx
        if(context.statusCode! >= 200 && context.statusCode! < 300){
          handler.next(context); //放过
          return;
        }
        // 说明异常
        handler.reject(DioException(requestOptions: context.requestOptions));
      },
      //   错误拦截器
      onError: (context,handler){
        handler.reject(context); // 直接抛出异常
      }
    ));
  }
  // 向外暴露一个get方法
 Future<Response<dynamic>> get(String url,{Map<String,dynamic>? params}){
    return _dio.get(url,queryParameters: params);
  }
}
相关推荐
SGAMERrain3 分钟前
# Nuxt 4 + MediaPipe:如何在浏览器中实现本地 AI 人脸分析?
前端
github_is_geeksKai1 小时前
浏览器端文件处理的边界:PDF 面单、GPX 隐私与 EPUB 修复
前端
李纲明1 小时前
微信小程序 getSystemInfo 提示已废弃、屏幕高度算错胶囊还对不齐?getWindowInfo 和 getDeviceInfo 怎么接才稳
javascript·微信小程序·小程序·小程序开发·devdiy
deli0072 小时前
从一句反馈到一次升级:Mystudy 的自进化实践
前端
Felven2 小时前
C. Brr Brrr Patapim
c语言·开发语言
FungLeo2 小时前
成为全栈·Vue 管理后台篇·用 fetch 建一层可控的请求内核
前端·javascript·vue.js·http·fetch·formdata·接口封装
用户84298142418103 小时前
ByteJS,让重要的 JavaScript 以字节码运行。
javascript
路溪非溪4 小时前
python标准库使用总结
开发语言·python
FungLeo4 小时前
成为全栈·Vue 管理后台篇·Vue Router如何守住多角色后台边界
前端·javascript·vue.js·rbac·vue router·路由守卫·前端权限
FungLeo4 小时前
成为全栈·Vue 管理后台篇·分页只显示第一页:从接口total 定位到前端修复
前端·vue.js·状态模式·分页·naive ui·服务端分页·前端问题排查