WPF项目实战视频《二》(主要为prism框架)

news2024/9/20 5:58:48

14.prism框架知识(1)

使用在多个平台的MVVM框架
新建WPF项目prismDemo
项目中:工具-NuGet包管理:安装Prism.DryIoc框架
在git中能看Prism的结构和源代码:git链接地址
例如:Prism/src/Wpf/Prism.DryIoc.Wpf/PrismApplication.cs这个类,就是在我们自己的prismDemo项目中需要更改继承使用。
在这里插入图片描述
报错:加上(更改)以下三行,重新生成并实现抽象类,上图报错即解决。
在这里插入图片描述
抽象类的实现:
在这里插入图片描述
管理扩展:添加"Prism Template Pack",就不用每次新建项目都添加一次prism了
添加扩展之后重新打开VS,新建项目:
在这里插入图片描述
打开之后就包含有prism和一些项目文件结构:
在这里插入图片描述

15.prism区域介绍(2)

主要内容:区域的概念和使用方法。
prism界面(.xaml文件中)中划分区域,可放入容器或者控件(动态的)

在.xaml中加入不同的子模块:(添加xsml子模块-主页面.png)
在这里插入图片描述

在views中新加三个用户控件:page1,page2,page3
现需要点击不同的按钮进入不同的页面:在ViewModels文件夹中修改MainWindowViewModel.cs文件:

public class MainWindowViewModel : BindableBase
{
    //执行不同的模块
    public DelegateCommand<string> OpenCommand { get; private set; }

    public MainWindowViewModel()
    {
        //执行不同的模块
        OpenCommand = new DelegateCommand<string>(Open);

    }

    private void Open(string obj)
    {
        switch (obj)
        {
            case "viewA": break;
            case "viewB": break;
            case "viewC": break;

        }
    }
}

绑定:.xaml中加入Content参数,进行绑定,传递参数用:CommandParameter参数

 <StackPanel Orientation="Horizontal">
     <Button Margin="5" Content="打开模块A" Command="{Binding OpenCommand}" CommandParameter="viewA"/>
     <Button Margin="5" Content="打开模块B" Command="{Binding OpenCommand}" CommandParameter="viewB"/>
     <Button Margin="5" Content="打开模块C" Command="{Binding OpenCommand}" CommandParameter="viewC"/>
 </StackPanel>

 <!--需要在.cs文件中连接需要使用Content-->
 <ContentControl Grid.Row="1" Content="{Binding Body}"/>

连接的Body的使用方式:在MainWindowViewModel.cs文件中

//定义一个跟MainWindow.xaml中绑定对应的Body
private object body;
public object Body
{
    get { return body; }
    set { body = value; RaisePropertyChanged(); }
}
....
//使用:
private void Open(string obj)
{
    switch (obj)
    {
        case "viewA": Body = new Page1(); break;	//此处使用
        case "viewB": Body = new Page2();  break;	//此处使用
        case "viewC": Body = new Page3();  break;	//此处使用
    }
}

(以上使用到Prism框架的自动查找上下文功能(views – viewModels))功能完成。


发现问题:view页面和viewModel后端处理,耦合性较大。需要降低耦合性------解决方式:使用prism插件:
在这里插入图片描述

在MainWindow.xaml中,需要注意prism的扩展插件自动生成了以下代码,注意不要重复添加,会报错。

<ContentControl prism:RegionManager.RegionName="ContentRegion"/>
<!--ui界面第二行内容-->
<!--需要在.cs文件中连接需要使用Content-->
<!--<ContentControl Grid.Row="1" Content="{Binding Body}"/>-->

<!--更改:使用prism的方式,降低耦合性,使用ContentRegion进行识别-->
<ContentControl Grid.Row="1" prism:RegionManager.RegionName="ContentRegion"/>

MainWindowViewModel.cs文件中:

//执行不同的模块
public DelegateCommand<string> OpenCommand { get; private set; }

private readonly IRegionManager regionManager;
//以下:使用prism的方式进行
public MainWindowViewModel(IRegionManager regionManager)
{
    //regionManager主要用于管理模块
    OpenCommand = new DelegateCommand<string>(Open);
    this.regionManager = regionManager;

}
private void  Open(string obj) 
{
    //regionManager.Regions["ContentRegion"].RequestNavigate("Page1"); //依赖注入的形式(在App中进行同步修改)识别Page1
    regionManager.Regions["ContentRegion"].RequestNavigate(obj);
}

Open(string obj) 会根据button commmand的内容对应填入obj的值

<Button Margin="5" Content="打开模块A" Command="{Binding OpenCommand}" CommandParameter="Page1"/>
<Button Margin="5" Content="打开模块B" Command="{Binding OpenCommand}" CommandParameter="Page2"/>
<Button Margin="5" Content="打开模块C" Command="{Binding OpenCommand}" CommandParameter="Page3"/>

在App.xaml.cs中依赖注入使ViewA能被识别
在这里插入图片描述
以上就完成了,使用prism插件,使用依赖注入,完成点击按钮切换页面的功能。
总结:首先通过IRegionManager接口获取当全局定义的可用区域,然后往这个区域动态的设置内容,设置方式:依赖注入

16.prism模块化介绍(3)

项目开发时往往会分多个模块进行开发,多个文件夹,本质上也是一个类库。此处讲解如何添加程序集到主程序的容器中:
以新建项目类库为例:基于整个解决方案,新建两个WPF类库项目,分别命名为:ModelA,和ModelB。
在ModelA和ModelB中添加Views文件夹,在ModelA文件夹中添加用户控件ViewA,在ModelB文件夹中添加用户控件ViewB。
程序集被识别成模块的特征:新定义一个类ModuleAprofile.cs,项目中添加prism.dryIoc包之后,使ModuleAprofile继承自IModule,并实现对应的接口方法(函数内容可为空,但是必须实现)
在这里插入图片描述
在上文此.cs文件中的RegisterTypes函数中加入其依赖注入:(ModelB中同样步骤进行添加)

public void RegisterTypes(IContainerRegistry containerRegistry)
{
    containerRegistry.RegisterForNavigation<ViewA>();
}

如何使之联系起来?:在主程序中加载ModuleA和ModuleB的方式有两种:一种是代码的方式,一种是配置文件的方式:

第一种:代码的方式

(此处需要注意使用插件创建的原始项目自动导入的是.net 6.0 而ModuleA和ModuleB导入的是8.0 需要版本变为一致,否则会报错。《项目->属性 中可修改版本》)
在主程序中加入项目引用,ModuleA和ModuleB
然后在App.xmls.cs中增加和修改:

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    在此加入依赖注入
    //containerRegistry.RegisterForNavigation<Page1>(); //后面的小括号可以重命名名字,不填的话就为<>中的Page1
    //containerRegistry.RegisterForNavigation<Page2>();
    //containerRegistry.RegisterForNavigation<Page3>();
}

//主程序中导入ModuleA和ModuleB,方法一:代码的模式
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
    moduleCatalog.AddModule<ModuleAprofile>();
    moduleCatalog.AddModule<ModuleBprofile>();
    base.ConfigureModuleCatalog(moduleCatalog);
}

在主程序的页面MainWindow.xaml中:修改为对应的ViewA,ViewB

<StackPanel Orientation="Horizontal">
    <Button Margin="5" Content="打开模块A" Command="{Binding OpenCommand}" CommandParameter="ViewA"/>
    <Button Margin="5" Content="打开模块B" Command="{Binding OpenCommand}" CommandParameter="ViewB"/>
    <Button Margin="5" Content="打开模块C" Command="{Binding OpenCommand}" CommandParameter="Page3"/>
</StackPanel>
第二种:加载动态库dll的方式

在App.xmls.cs中增加和修改:

//第二种方式:加载动态库的方式CreateModuleCatalog
protected override IModuleCatalog CreateModuleCatalog()
{
    return new DirectoryModuleCatalog() { ModulePath = @".\Modules" };
}

把moduleA文件目录下bin下生成的ModelA.dll和对应的ModelB.dll放在主程序下的bin/debug目录下。

第三种:加载配置文件的方式

此不举例

17.prism导航功能(4)

之前的open有使用在区域之间进行模块的切换。此章详细讲解。
之前使用过的导航方式:
App.xaml:

 protected override void RegisterTypes(IContainerRegistry containerRegistry)
 {
     在此加入依赖注入
     containerRegistry.RegisterForNavigation<Page1>(); //后面的小括号可以重命名名字,不填的话就为<>中的Page1
     //containerRegistry.RegisterForNavigation<Page2>();
     //containerRegistry.RegisterForNavigation<Page3>();
 }

ModuleA和ModuleB项目分别添加ViewModels文件夹存放对应的ViewAViewModels.cs和ViewBViewModels.cs文件
在这里插入图片描述
需要在对应的view.xaml文件中加入prism自动识别
在这里插入图片描述
也可以手动的绑定:ModuleAprofile.cs中(较为常用)
在这里插入图片描述
绑定之后,就可以在对应viewmodule中实现对应的业务代码


添加导航的参数:

在主程序的module中MainWindowViewModel.cs文件,加入导航的参数
在这里插入图片描述
在ModuleA模块中识别出来:
使ModuleA下的ModuleAViewModules.cs 使之继承自INavigationAware然后实现其对应的接口
在这里插入图片描述

//添加数据有关的基类BindableBase
public class ViewAViewModules : BindableBase,INavigationAware
{
    private string title;
    public string Title{	//要注意这里不能使用默认的private  要改为public 否则回报错
        get { return title; }   
        set { title = value; RaisePropertyChanged(); }
    }
        public bool IsNavigationTarget(NavigationContext navigationContext)
        {
            //每次重新导航的时候,是否重用原来的实例
            return false;		//这里使用true的话下次从ModelB切换到ModelA不会进行覆盖。
            //throw new NotImplementedException();
        }

        public void OnNavigatedFrom(NavigationContext navigationContext)
        {
            //throw new NotImplementedException();
            //拦截导航请求
        }
    public void OnNavigatedTo(NavigationContext navigationContext)
    {
        //throw new NotImplementedException();
        if(navigationContext.Parameters.ContainsKey("Title")) {
            //用来接收viewmodule传递的参数,传递什么类型就用什么类型接收
            Title = navigationContext.Parameters.GetValue<string>("Title");
        }
    }
}

使用OnNavigatedFrom进行导航拦截,在进行切换时会调用此函数
方式:之前ViewAViewModules继承自INavigationAware,可以改为IConfirmNavigationRequest,实现其接口ConfirmNavigationRequest,此接口主要用于验证是否允许切换

public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
{
    //验证是否允许切换
    //throw new NotImplementedException();
    bool result = true;
    if(MessageBox.Show("确认导航?","提示",MessageBoxButton.YesNo)==MessageBoxResult.No)
    {
        //如果是no 那么
        result = false;
    }
    continuationCallback(result);   //通过这个委托把这个结果(是否拦截导航)传进去
}

类似的导航功能还有:返回上一步:导航日志(此处省略)

18.prism对话服务(5)

也就是弹窗的功能。
在祝程序的MainWindow.xaml中,添加两个按钮进行弹窗触发。在主程序中添加窗口ViewC.cs,在open函数中new此类,点击时则会有窗口出现。
在这里插入图片描述
但是特定的页面依赖于特定的实现,耦合度太高,这个问题如何去解决?-------依赖注入的方式(同上band).
将之前在主项目文件中创建的窗口ViewC.cs删掉,在ModuleA中新建一个用户控件ViewAA.cs,自定义控件的分布,提示和其他按钮。

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="auto" />
        <RowDefinition/>
        <RowDefinition Height="auto" />
    </Grid.RowDefinitions>

    <TextBlock Text="温馨提示" />
    <TextBlock Grid.Row="1" Text="hello~~~~" FontSize="80"  VerticalAlignment="Center"/>
    <StackPanel Orientation="Horizontal" Grid.Row="2"><!--水平排列-->
        <Button Content="取消" />
        <Button Content="确认" />
    </StackPanel>
</Grid>

在这里插入图片描述
然后注入依赖:之前注入导航是:RegisterForNavigation 现在注入对话框是:RegisterDialog
在这里插入图片描述
加入ViewAA对应的ViewAAViewModules:继承自IDialogAware,然后点击实现对应接口。

public class ViewAAViewModules : IDialogAware
{
    //对话框
    //public string Title => throw new NotImplementedException();//修改,不抛异常
    public string Title {  get; set; }

    public event Action<IDialogResult> RequestClose;

    public bool CanCloseDialog()
    {
        //允许被关闭
        return true;
        //throw new NotImplementedException();
    }

    public void OnDialogClosed()
    {
        //关闭窗口
        DialogParameters keys = new DialogParameters();
        keys.Add("Value", "viewAA:Dialog");
        RequestClose?.Invoke(new DialogResult(ButtonResult.OK, keys));
        //throw new NotImplementedException();
    }

    public void OnDialogOpened(IDialogParameters parameters)
    {
        //接收弹窗中的参数
        //throw new NotImplementedException();
    }
}

然后在ModuleAprofile.cs注册依赖的地方,将viewAA和viewAAviewmodules进行绑定

在这里插入图片描述
对话框传递参数:主项目中MainWindowViewModel.cs中写入需要传递的参数。

//对话框
private readonly IDialogService dialogService;
public MainWindowViewModel(IDialogService dialogService)
{
    //regionManager主要用于管理模块
    OpenCommand = new DelegateCommand<string>(Open);
    this.dialogService = dialogService;

}
private void Open(string obj)
{
    DialogParameters keys = new DialogParameters();
    keys.Add("Title", "测试弹窗");		//传递参数
    dialogService.ShowDialog(obj,keys,callback =>
    {
        //回调方法
    });
}

ViewAAViewModules中接收参数的函数OnDialogOpened修改:

public void OnDialogOpened(IDialogParameters parameters)
{
    //接收弹窗中的参数
    //throw new NotImplementedException();
    Title=parameters.GetValue<string>("Title");
}

点击【打开对话框A】,则显示“测试弹窗”-弹窗
在这里插入图片描述
现在把【确认/取消按钮加入对应的功能】:
ViewAAViewModules.cs写构造函数

public DelegateCommand CancelCommand {  get; set; } //取消
public DelegateCommand SaveCommand { get; set; }    //保存

public ViewAAViewModules() {
    CancelCommand = new DelegateCommand(Cancel);
    SaveCommand = new DelegateCommand(Save);
}

private void Save()
{
    //新生成方法-保存
    OnDialogClosed();
}

private void Cancel()
{
    //新生成方法-取消
    RequestClose?.Invoke(new DialogResult(ButtonResult.No));
}

ViewAA给按钮中绑定command

 <Button Content="取消"  Command="{Binding CancelCommand}"/>
 <Button Content="确认"  Command="{Binding SaveCommand}" />

在之前MainWindowViewModel的回调函数中进行判断

 private void Open(string obj)
 {
     DialogParameters keys = new DialogParameters();
     keys.Add("Title", "测试弹窗");
     dialogService.ShowDialog(obj,keys,callback =>
     {
         //回调方法,点击之后获取点击的结果
         if (callback.Result == ButtonResult.OK) {
             //如果点击了ok那么获取传递的值
             string result = callback.Parameters.GetValue<string>("Value");
         }
     });
 }

实现点击确定按钮(OK)进行数据传递,点击取消按钮(NO)不做操作

好处:实现业务代码的简化,耦合性降低

19.prism发布订阅(6)

创建Event文件夹,创建MessageEvent.cs文件,消息模型。
在这里插入图片描述
使用:ViewAAViewModules.cs构造函数注入
在这里插入图片描述
例如,在ViewAA.xaml中,进行订阅:

在这里插入图片描述
进行测试:
修改ViewAAViewModules.cs
在这里插入图片描述
测试:ViewAAViewModules.cs点击取消按钮发布信号
在这里插入图片描述
实现效果:点击打开对话框A–》弹出测试弹窗 --》点击测试弹窗取消–》
弹出接收消息弹窗
在这里插入图片描述
取消订阅功能实现:点击取消,接收到消息之后取消该消息的订阅,则第二次点击取消按钮不会再弹窗。ViewAA.xaml.cs文件
在这里插入图片描述
扩展:也可以写复杂类型的对象:

namespace ModelA.Event
{
    public class MessageEvent:PubSubEvent<string >
    {
        //主要用来传递string类型的消息
    }

    public class TestEvent : PubSubEvent<Test>
    {

    }

    public class Test
    {
        public string Id {  get; set; }
        public string Name { get; set; }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1942453.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux中tomcat下载教程

一.安装tomcat 1.安装 EPEL 仓库&#xff1a; sudo yum install epel-release2.安装 Tomcat&#xff1a; sudo yum install tomcat3.启动 Tomcat 服务&#xff1a; sudo systemctl start tomcat4.启用 Tomcat 服务开机启动&#xff1a; sudo systemctl enable tomcat5.检查…

SpringCloud 环境工程搭建

SpringCloud 环境&工程搭建 文章目录 SpringCloud 环境&工程搭建1. SpringCloud介绍2. 服务拆分原则2.1 单一职责原则2.2 服务自治2.3 单向依赖2.4 服务拆分示例 3. 数据准备4. 工程搭建4.1 创建父工程4.2 创建子工程4.2.1 子项目-订单服务4.2.2 子项目-商品服务 4.3 完…

物联网专业创新人才培养体系的探索与实践

一、引言 随着物联网&#xff08;IoT&#xff09;技术的迅猛发展&#xff0c;物联网领域的人才需求日益增加。物联网技术作为新一轮信息技术革命的核心&#xff0c;已经渗透到社会生活的各个领域&#xff0c;对推动经济转型升级、提升国家竞争力具有重要意义。因此&#xff0c…

Redis 7.x 系列【26】集群模式动态扩容、动态缩容

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Redis 版本 7.2.5 源码地址&#xff1a;https://gitee.com/pearl-organization/study-redis-demo 文章目录 1. 动态扩容1.1 安装、启动1.2 加入新节点1.3 分配哈希槽1.4 加入从节点 2. 缩容2.1 删…

PHP场地预约共享茶室棋牌室小程序系统源码

&#x1f375;&#x1f3b2;【聚会新宠】场地预约神器&#xff0c;共享茶室棋牌室小程序大揭秘&#xff01;&#x1f389; &#x1f3e1;【开篇&#xff1a;告别繁琐&#xff0c;聚会新选择】&#x1f3e1; 还在为找不到合适的聚会场地而烦恼吗&#xff1f;想要一个既私密又舒…

python+onlyoffice+vue3项目实战20240722笔记,环境搭建和前后端基础代码

开发后端 先创建data目录,然后在data目录下创建一个test.docx测试文档。 后端代码: import json import req import api from api import middleware, PlainTextResponseasync def doc_callback(request):data = await api.req.get_json(request)print("callback ==…

微信小程序-CANVAS写入图片素材、文字等数据生成图片

微信小程序中&#xff0c;CANVAS写入图片素材、文字等数据生成图片&#xff0c;最终可将生成的 base64 格式图片保存至相册操作 Tips&#xff1a; 1、canvas 标签默认宽度 300px、高度 150px canvas 生成图片时&#xff0c;写入图片素材、文字等数据前&#xff0c;需要根据实…

git的一些使用技巧(git fetch 和 git pull的区别,git merge 和 git rebase的区别)

最近闲来无聊&#xff0c;虽然会使用git操作&#xff0c;但是 git fetch 和 git pull 的区别&#xff0c;git merge 和 git rebase的区别只是一知半解&#xff0c;稍微研究一下&#xff1b; git fetch 和 git pull 的区别 git fetch git fetch 是将远程仓库中的改动拉到本地…

鸿蒙仓颉语言【扩展Redis仓颉语言客户端】

2. 扩展Redis仓颉语言客户端 2.1 Redis命令处理模块的架构 Redis命令处理的架构图如下&#xff1a; RedisCommand类 Redis命令的实现类 包含以下成员&#xff1a; commandType: Redis命令的名称 commandArgs: Redis命令的参数列表 response: Redis命令的响应消息&#xff…

【学习笔记】无人机系统(UAS)的连接、识别和跟踪(十)-无人机A2X服务

引言 3GPP TS 23.256 技术规范&#xff0c;主要定义了3GPP系统对无人机&#xff08;UAV&#xff09;的连接性、身份识别、跟踪及A2X&#xff08;Aircraft-to-Everything&#xff09;服务的支持。 3GPP TS 23.256 技术规范&#xff1a; 【免费】3GPPTS23.256技术报告-无人机系…

当当网数据采集:Scrapy框架的异步处理能力

在互联网数据采集领域&#xff0c;Scrapy框架以其强大的异步处理能力而著称。Scrapy利用了Python的异步网络请求库&#xff0c;如twisted&#xff0c;来实现高效的并发数据采集。本文将深入探讨Scrapy框架的异步处理能力&#xff0c;并展示如何在当当网数据采集项目中应用这一能…

npm 安装报错(已解决)+ 运行 “wue-cli-service”不是内部或外部命令,也不是可运行的程序(已解决)

首先先说一下我这个项目是3年前的一个项目了&#xff0c;中间也是经过了多个人的修改惨咋了布置多少个人的思想&#xff0c;这这道我手里直接npm都安装不上&#xff0c;在网上也查询了多种方法&#xff0c;终于是找到问题所在了 问题1&#xff1a; 先是npm i 报错在下面图片&…

下拉菜单过渡

下拉过渡&#xff0c;利用Y轴的transform&#xff1a;scaleY(0) —》transform&#xff1a;scaleY(1) 代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8" /><meta name"viewport" cont…

实战:MyBatis适配多种数据库:MySQL、Oracle、PostGresql等

概叙 很多时候&#xff0c;一套代码要适配多种数据库&#xff0c;主流的三种库&#xff1a;MySQL、Oracle、PostGresql&#xff0c;刚好mybatis支持这种扩展&#xff0c;如下图所示&#xff0c;在一个“namespace”&#xff0c;判断唯一的标志是iddatabaseId&#xff0c;刚好写…

学习vue3的搭建

Vue3 Vite项目构建 环境准备 1. NodeJs安装 安装NodeJs&#xff0c;安装成功后&#xff0c;以管理员身份打开命令行&#xff0c;输入命令 node -v查看NodeJs版本&#xff1b;输入命令 npm -v查看npm版本。 2. 安装cnpm 因为npm是国外的&#xff0c;下载资源的时候会翻墙&…

mysql面试(一)

前言 从今天开始&#xff0c;更新一些mysql的基础知识&#xff0c;面试会遇到的知识点之类的内容。比如四个隔离级别&#xff0c;mvcc机制&#xff0c;三大日志&#xff0c;索引&#xff0c;B树的形成等等&#xff0c;从数据库的底层来剖析索引和树是怎么形成的&#xff0c;以…

LeetCode //C - 257. Binary Tree Paths

257. Binary Tree Paths Given the root of a binary tree, return all root-to-leaf paths in any order. A leaf is a node with no children. Example 1: Input: root [1,2,3,null,5] Output: [“1->2->5”,“1->3”] Example 2: Input: root [1] Output: […

服务器利用宝塔面板部署Django项目

目录 1. 使用命令启动Django项目1.1 使用 Xshell 连接服务器1.2 安装Anaconda1.3 启动Django项目1.4 使用tmux实现项目的后台运行 2. 使用Python项目管理器部署项目2.1 安装宝塔面板和软件2.2 添加站点2.3 上传项目文件2.3.1 收集静态文件2.3.2 生成依赖文件 2.4 安装安装Pytho…

如何查看Kafka的偏移量offset

本文介绍三种方法查看Kafka的偏移量offset。 1. API&#xff1a;ConsumerRecord的offset()方法查看offset。 2. API&#xff1a;KafkaConsumer的position(TopicPartition partition)方法查看offset。 3. 命令行&#xff1a;kafka-consumer-groups.sh命令查看offset。 前提条…

OpenHarmony 入门——ArkUI 自定义组件之间的状态装饰器小结(一)

文章大纲 引言一、状态管理概述二、基本术语三、状态装饰器总览 引言 前面说了ArkTS 是在TypeScript基础上结合ArkUI框架扩展定制的&#xff0c;状态管理中的各种装饰器就是扩展的功能之一&#xff0c;可以让开发者通过声明式UI快速高效实现组件之间的数据同步&#xff0c;至于…