rust前端web开发框架yew使用

构建完整基于 rust 的 web 应用,使用yew框架

trunk 构建、打包、发布 wasm web 应用

安装后会作为一个系统命令,默认有两个特性开启

  • rustls - 客户端与服务端通信的 tls 库
  • update_check - 用于应用启动时启动更新检查,应用有更新时提示用户更新。
  • native-tls 需要指定开启,使用系统原生的 tls 用于客户端;使用 openssl 用于服务端
$> cargo install --locked trunk

创建一个 rust web 项目cargo new rust-yew-web,项目根目录下创建index.html

没有任何内容,尝试一下trunk build,可以看到dist目录

$> trunk buil

trunk-build.png

安装yew开始 web 程序

用于创建使用 webAssembly 的多线程 web 应用框架。

$> cargo add yew --features "csr"

yew不会默认指定特性,我们做的是 web 端开发,所以指定开启csr。其他的还包括 ssr - 服务端渲染;hydration - 混合开发,支持客户端、服务端渲染。

src/main.rs定义方法app渲染 html,html!宏可以定义类似 jsx 语法的视图结构。

use yew::prelude::*;#[function_component(App)]
fn app() -> Html {html! {<h1>{"hello, trunk/yew!"}</h1>}
}fn main() {yew::Renderer::<App>::new().render();
}

#[function_component(App)] 属性宏使一个普通的 rust 函数变成了一个函数组件,这个组件必须返回 html.#[function_component]接受一个组件名称,这里我们定义的是App

函数组件可以接受一个参数props: &Props用于组件之间传递数据。它通常是一个没有状态的静态组件。

启动,通过trunk serve --open启动一个服务,直接打开浏览器

$> trunk serve --open

可以看到浏览器输出,修改内容会重新编译,实时刷新页面。

yew-start.png

可以在Trunk.toml中配置启动服务的地址、端口

# The address to serve on LAN.
address = "127.0.0.1"
# The address to serve on WAN.
# address = "0.0.0.0"
# The port to serve on.
port = 8000

组件语法,使用注意项

可以注意到组件中<h1>{"hello, trunk/yew!"}</h1> 文本字符展示需要使用{}括起来。

  • 组件只能有一个根节点,节点必须是闭合的。如果不需要渲染根节点,可以使用<></>,使用block也行

        html! {<><h1>{"hello, trunk/yew!"}</h1><h2>{"good!"}</h2></>}
    
  • 循环渲染,渲染的每一个节点业务需要返回html!。最后通过collect消费掉。

       // 需要渲染的字段let names = ["admin", "test", "hboot"];// 渲染片段let names = names.iter().map(|name| {html! {<p>{format!("{name}")}</p>}}).collect::<Html>();
    

    在组件的html返回中使用,通过{},可以看到浏览器中的输出。

      html! {<><h1>{"hello, trunk/yew!"}</h1><h2>{"good!"}</h2>{names}</>}
    

    也可以直接在html! {}中直接使用循环渲染。这里可以使用另一种语法{for ...}来替代消费.collect()

       // 渲染片段let names = names.iter().map(|name| {html! {<p>{format!("{name}")}</p>}});//     .collect::<Html>();html! {<>{for names}</>}
    
  • 属性绑定,给节点绑定动态的 class,通过{}。这里只演示了变量绑定,动态的则需要hook声明。

      let active = "active";html! {<><h2 class={active}>{"good!"}</h2></>}
    
  • 条件判断,通过if判断

       let bool = true;html! {<>{if bool{html!{<span>{"yes"}</span>}}else{html!{<span>{"no"}</span>}}}</>}
    

    在未来更新后,内部的html!可能就不需要了,现在仍需要加上。表明类型是 html。

关于组件 - Componenttrait

上面我们实现一个组件App,通过#[function_component]属性宏转变 rust 函数为一个组件。也可以通实现Componenttrait,来实现组件的功能。

重新创建一个模块src/user.rs,创建一写关于个人信息的组件

use yew::prelude::*;// 定义用户结构体
pub struct User {name: String,
}impl Component for User {type Message = ();type Properties = ();fn create(ctx: &Context<Self>) -> Self {Self {name: "hboot".to_string(),}}fn view(&self, ctx: &Context<Self>) -> Html {html! {<div class="user-center"><h3>{self.name.clone()}</h3></div>}}
}

Component作为一个 trait,只要实现了就可以作为一个函数组件渲染到视图中。必须要实现的方法createview,两个必须要声明的类型Message\Properties。还有一些其他的方法

  • create 在组件创建后调用,用于初始化。

  • view 定义组件视图,语法类似于 jsx。 create方法跟随 view 方法的调用;view 方法不总是跟随updatechanged方法,内部做了一些渲染优化。

  • type Message: 'static 用来声明消息类型,使得组件变成动态组件、可交互。它通过枚举来定义消息类型。

  • type Properties: Properties 定义组件的属性,它接受来自上文context的消息,不一定是父组件。触发组件的重新渲染。

  • update 可选,交互式消息触发时的钩子函数,在这里处理逻辑。返回bool来定义是否触发组件更新。

  • changed 可选,定义属性变更时的钩子函数。返回bool来定义是否触发组件更新。

  • rendered 可选,在组件渲染完成,还未更新到页面时调用。在view之后

  • destroy 可选,在组件销毁卸载之前调用。

  • prepare_state 可选,在服务端渲染后,组件被渲染前调用。

main.rs中导入使用,可以看到页面上已经出现了展示内容。

mod user;
// 使用组件
use user::User;fn app() -> Html {// ...html! {<><User /></>}
}

添加一个事件,通过点击 button 来改变当前的用户名。就需要实现update方法来处理交互消息

// 定义消息类型
pub enum Msg {UpdateName,
}
// ...
impl Component for User {// ...fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {match msg {Msg::UpdateName => {self.name = "admin".to_string();true}}}fn view(&self, ctx: &Context<Self>) -> Html {html! {<div class="user-center"><button οnclick={ctx.link().callback(|_| Msg::UpdateName )}>{"更新"}</button><h3>{self.name.clone()}</h3></div>}}
}

update方法中,通过接收到的msg消息类型来匹配需要执行的逻辑。当然可以将处理逻辑抽离提供一个方法进行调用。在view中添加了一个 button 作为按钮触发点击事件,通过onclick监听点击事件,ctx.link().callback()来发起一个事件,这有点像 react 的 redux。

数据传递,单项数据流

首先要接收来自父组件的数据,我们定义一个Props类型,props 需要过程宏derive来实现Properties \ PartialEq,然后定义type Properties = Props

#[derive(Properties, PartialEq)]
pub struct Props {pub age: i32,
}impl Component for User {// ...type Properties = Props;//...fn view(&self, ctx: &Context<Self>) -> Html {html! {<div class="user-center"><h3>{format!("姓名:{}",self.name.to_string())}</h3><h4>{format!("年龄:{}",ctx.props().age)}</h4></div>}}
}

定义完Properties,就需要在用到组件的地方增加传参。默认是必传的

 html! {<><User age={30} /></>}

通过属性宏来设置属性的状态,这样就可以不必传:

  • #[prop_or_default] 默认初始化值。
  • #[prop_or(value)] 使用默认值value指定默认值。
  • #[prop_or_else(fn)] 指定初始化值函数,没有传值时会调用。函数签名FnMut()-> T
#[derive(Properties, PartialEq)]
pub struct Props {#[prop_or(28)]pub age: i32,
}

这样就可以不必传了。我们可以在父组件使用props!宏来定义子组件的 props,然后传给子组件,为了标识 props,我们把 user 中的 props 改名为UserProps

main.rs:

use yew::props;// ...
use user::{User, UserProps};fn app() -> Html {// ...let user_props = props! {UserProps {age:30}};html! {<><User ..user_props /></>}
}

可以看到props!宏可以接受多个 props。定义完之后通过..user_props绑定到子组件上。注意是两个点..

两个定义 props 字段类型的建议:

  1. 不要使用String类型,而是使用&str。因为 String 类型的复制消耗大
  2. 不要使用内部可变性的智能指针,这会导致组件不知掉什么时候需要更新。

子组件更新父组件状态

子组件通过事件回调的方式更新父组件的状态。我们定义父组件更新age的方法,然后在子组件触发调用更新

main.rs,因为需要更新 age,所以需要声明 age 为父组件的一个状态数据,在函数组件中使用use_state定义。然后声明更新方法update_age提供给子组件调用。

let age = use_state(|| 30);// 点击更新年龄
let update_age: Callback<()> = {let age = age.clone();Callback::from(move |_| age.set(26))
};html! {<>// ...<User age={*age} on_update_age={update_age} /></>
}

因为 props 的不可变性,我们使用props!创建的传参改为传统方式。

*age 解引用获取指向的值。通过age.set()方法更新值。子组件 props 增加接受回调函数,我们这个回调不接受参数,所以给一个()

user.rs中:

pub enum Msg {// ...UpdateAge,
}#[derive(Properties, PartialEq)]
pub struct UserProps {// ...pub on_update_age: Callback<()>,
}impl Component for User {// ...fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {match msg {// ...Msg::UpdateAge => {ctx.props().on_update_age.emit(());false}}}fn view(&self, ctx: &Context<Self>) -> Html {html! {<div class="user-center">// ...<button οnclick={ctx.link().callback(|_| Msg::UpdateAge )}>{"更新age"}</button><h4>{format!("年龄:{}",ctx.props().age)}</h4></div>}}
}

首先增加了一个 msgUpdateAge,有 button 触发点击回调Msg::UpdateAge,在update中匹配消息类型,调用来自父组件的回调方法ctx.props().on_update_age.emit(()),不是直接调用哦,而是通过.emit()可以传值,因为我们不需要,所以给一个元组。

组件内部嵌套html

上面子组件更新父组件的状态感觉很费劲,既然状态在父组件的我们可以通过在子组件调用时嵌套 html 的方式增加子组件视图。

main.rs

let update_age = {let age = age.clone();Callback::from(move |_| age.set(26))
};html! {<><User><button οnclick={update_age}>{"更新age"}</button><h4>{format!("年龄:{}",*age)}</h4></User></>
}

在父组件渲染就没有那么多的弯弯绕绕。

修改子组件user.rs,移除掉之前的回调函数的定义。增加 props 类型children:Html,就可以直接在渲染时访问。

#[derive(Properties, PartialEq)]
pub struct UserProps {// ...pub children: Html,
}impl Component for User {fn view(&self, ctx: &Context<Self>) -> Html {html! {<div class="user-center">// ...{ctx.props().children.clone()}</div>}}
}

组件hooks

上面已经使用了一个 hookuse_state用于管理数据状态。这样类比 react,不就是函数组件和类组件的区别吗,感觉就容易上手很多。

可以使用已经定义好的 hook,还可以自定义 hook。

yew hook 文档

  • use_state 管理函数组件的数据状态,只要设置新的值,就会触发组件重新渲染。

  • use_state_eq 设置新值,需要比较旧值是否相等。不相等才出发组件渲染,定义的数据结构需要实现PartialEqtrait

  • use_effect 副作用钩子函数,在组件渲染完成后调用。

      use log::{info, Level};#[function_component(App)]fn app() -> Html {// ...use_effect(move || {// 渲染完成执行info!("render!")});// 点击更新年龄let update_age = {let age = age.clone();Callback::from(move |_| age.set(30))};// ...}
    

    我们点击视图中的更新年龄,会一直调用update_age,虽然值没有发生变化,但是组件仍会重新渲染。

    为了防止不必要的重复渲染,可以声明变量使用use_state_eq

      // let age = use_state(|| 30);let age = use_state_eq(|| 30);
    

    我们再次测试点击更新,发现没有执行副作用函数use_effect的逻辑。还有另一种方式就是use_effect_with只有它接收的依赖变量发生变化时才触发调用。

  • use_effect_withuse_effect,接受依赖,依赖变更时,才触发。

    let age = use_state(|| 30);//...
    let with_age = age.clone();
    use_effect_with(with_age, move |_| {// 渲染完成执行info!("dep render!")
    });
    

    点击更新时,没有触发use_effect_with钩子输出,初始时触发。

  • use_force_update 手动强制重新渲染组件

  • use_memo 优化计算,只有在依赖项发生变更时才会重新执行计算。

  • use_callback 优化渲染,使得子组件不受父组件数据状态变化而重新渲染。

  • use_context 捕获上下文的值,跨组件共享数据。

  • use_mut_ref 获取值的可变引用,但不会引起组件重新渲染。

  • use_node_ref 用来访问 DOM 元素,ref绑定

  • use_reducer 共享计算逻辑,可以在不同的组件内通过触发action来调用处理函数
    和 react 的 redux 的类似,定义数据结构必须实现Reducibletrait

    定一个UserInfo结构,存储用户个人信息,定一个 actionUserInfoAction触发 age 的值更新。实现Reducibletrait,定义 type Action = UserInfoAction.

      pub struct UserInfo {pub age: i32,}// 定义操作的 actionpub enum UserInfoAction {UpdateAge(i32),}impl Default for UserInfo {fn default() -> Self {Self { age: 28 }}}// action reducerimpl Reducible for UserInfo {type Action = UserInfoAction;fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {match action {UserInfoAction::UpdateAge(age) => {info!("update age --- {age}");Self { age }.into()}}}}
    

    通过reduce方法接收分发过来的 action,并增加处理逻辑。这里使用了Rc引用计数,维护多引用值的可变性。

    然后在main.rs引入使用,使用use_reducerUserInfo::default初始化状态.dispatch方法分发事件。

    use std::rc::Rc;
    mod userInfo;
    use userInfo::{UserInfo, UserInfoAction};#[function_component(App)]
    fn app() -> Html {// ...let age = use_state(|| 30);let user_info = use_reducer(UserInfo::default);// 点击更新年龄let update_age = {let age = age.clone();let user_info = user_info.clone();let cb1 = Rc::new(Callback::from(move |_| age.set(30)));let cb2 = Rc::new(Callback::from(move |_| {user_info.dispatch(UserInfoAction::UpdateAge(30))}));//Callback::from(move |_| {cb1.emit(());cb2.emit(());})};// ...
    }
    

    在一个方法中同时处理多个事件时,通过Rc<T>引用计数确保Callback::from闭包调用的所有权。在第三个Callback::from通过emit()方法同时触发多个事件。

  • use_reducer_eq 设置值时,比较新旧值是否相等。

其他

  • rust 本身不支持解析 css 样式,只能通过外部样式来调整。给节点增加类或 id。

       html! {<h1 class="info">{"hello, trunk/yew!"}</h1>}
    

    再利用trunk的功能,加载外部样式。

    也可以直接通过style属性,你要是直接写一串字符串 css 样式,绑定到 style 也行,就是不好维护

      html! {<h1 class="info" style="color:red;">{"hello, trunk/yew!"}</h1>}
    

    需要动态的 class 设置,只能通过classes!()宏管理,动态添加、移除、切换,参数可以是 list、字符串、实现了Into<Classes>的类型

    新增一个is_active状态,通过 button 点击事件更新值,再通过条件语句判断增加 class。Classess用于声明和管理类属性。

    let mut class = Classes::from("info");
    if *is_active {class.push("active")
    };html! {<><button οnclick={change_active}>{"active"}</button><h1 class={class} style="color:red;">{"hello, trunk/yew!"}</h1></>}
    
  • html!宏那边定义的节点元素是小写的,如果需要使用大写,则可以这样html! { <@{"myBook"}> </@> },也可以用于动态 tag 标签设置

  • 动态设置属性的,通过Some(None)定义字段,如果设置为None,这个属性则不会被设置。

  • 阻止事件冒泡,通过事件的回调参数event,可以调用event.prevent_default().stopPropagation()阻止默认事件及事件冒泡。

相关的开发crate

开发时,需要有一些工具库帮助我们进行开发,方便我们调试、验证逻辑;

  1. console_error_panic_hook 上一篇文章已经讲过了,可以帮助我们在浏览器控制台输出错误的具体信息

  2. console_log 在浏览器控制台输出信息
    安装依赖:

$> cargo add log console_log

main.rs

use log::{info, Level};fn main() {let _ = console_log::init_with_level(Level::Debug);info!("render web page");yew::Renderer::<App>::new().render();
}
  1. wasm-bindgen 用于前端与 js 交互的桥梁。上一篇文章里写过。

  2. js-sys 基于wasm-bindgen提供的 js 全局 Api 对象,及属性绑定。例如 Array、Object、Reflect 等
    依赖安装:

     cargo add js-sys
    

    新建一个数组,添加一个值,并取值

     use js_sys::Array;use wasm_bindgen::prelude::*;fn main() {// ...let arr = Array::new();arr.push(&JsValue::from(10));info!("{:?}", arr.get(0).as_f64().unwrap());}
    

    js 所有 api 调用传入的数据类型,都需要是JsValue,它定义在wasm_bindgen中;

  3. web-sys 基于wasm-bindgen 提供的原始浏览器提供的接口,例如 DOM、WebGL、CSSOM 等。
    安装依赖:

    $> cargo add web-sys
    

    为了不影响打包速度,我们将需要用到的 api 特性列在依赖特性中.这里看有哪些可以导入使用的对象

    [dependencies.web-sys]
    version = "0.3.69"
    features = ["Window"]
    

    这样我们就可以使用到Window对象下的所有方法、属性。使用Window.alert()

     use wasm_bindgen::prelude::*;#[wasm_bindgen]
    pub fn alert() {let window = web_sys::window().unwrap();window.alert_with_message("alert!!").unwrap();
    }
    

    然后在事件回调函数中调用alert(),想要弹出自定义消息,需要调用alert_with_message,具体 API 有哪些可以用的方法,查看文档。

  • gloo 是一个工具包,上面的js-sysweb-sys都是底层的 api 封装,理解和使用都很困难,而gloo正是为了解决这个困难,提供简单易用的 api。

简单介绍了trunk打包工具,以及 web 库yew的概念知识,基本使用。下一篇则构建一个可以用于开发的脚手架。

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

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

相关文章

Linux——终端

一、终端 1、终端是什么 终端最初是指终端设备&#xff08;Terminal&#xff09;&#xff0c;它是一种用户与计算机系统进行交互的硬件设备。在早期的计算机系统中&#xff0c;终端通常是一台带有键盘和显示器的电脑&#xff0c;用户通过它输入命令&#xff0c;计算机在执行命…

SpringBoot引入Layui样式总是出现404

一般出现Layui样式文件如css&#xff0c;js404的错误 解决方案 &#xff08;1&#xff09;首先将其中的静态资源下载resources/static中 &#xff08;2&#xff09;在启动类中重写方法 package com.gq.booksystem;import org.mybatis.spring.annotation.MapperScan; import …

centOS 7.9操作

名称日期版本作者centOS7.9操作2024.4.271.0lll 实验题目&#xff1a; 创建一个用户。 在创建的用户中再创建一个2024的目录。 在2024的下在创建一个 1---10的目录&#xff0c;再创建一个a--z.txt的文件。 在创建一个2024bak的目录。 再将当前用户的所有文件备份到2024ba…

【算法学习】线段树基础版

一 线段树 1.概念 线段树可以理解为一个二叉树&#xff0c;如果是利用线段树求区间的和&#xff0c;那么每个结点的权值维护的是结点所维护区间的和&#xff0c;再将该区间一分为二&#xff0c;分别交由左右儿子维护。 拿区间1 - 4的和来举例子&#xff0c; 根结点维护的是区…

JavaEE——Spring Boot入门

目录 &#x1f4da; JavaEE——Spring Boot入门 &#x1f527; 1. 新建Spring Boot项目 &#x1f6e0; 2. 添加pom依赖 &#x1f4dd; 3. 添加application.yml文件 &#x1f4c2; 4. 创建Dao层 &#x1f527; 5. 创建Service层 &#x1f5a5;️ 6. 创建Controller层及HT…

使用ClassFinal实现springboot项目jar包加密

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

docker容器技术篇:集群管理实战mesos+zookeeper+marathon(二)

docker集群管理实战mesoszookeepermarathon&#xff08;二&#xff09; 一 实验环境 操作系统&#xff1a;centos7.9 二 基础环境配置以及安装mesos 安装过程请点击下面的链接查看&#xff1a; 容器集群管理实战mesoszookeepermarathon&#xff08;一&#xff09; 三 安装…

今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 4月27日,星期六

每天一分钟&#xff0c;知晓天下事&#xff01; 2024年4月27日 星期六 农历三月十九 1、 教育部&#xff1a;深入实施学生欺凌防治专项行动&#xff0c;对所有中小学校开展起底式大排查。 2、 商务部等七部门联合印发《汽车以旧换新补贴实施细则》&#xff0c;购车最高补贴1万…

【VBA】获取指定目录下的Excel文件,并合并所有excel中的内容。

1.新建一个excel表格。并创建两个Sheet&#xff0c;名字分别命名为FileList 和 All information。 2.按ALTF11进入 VBA编程模块&#xff0c;插入模块。 3.将如下 第五部分代码复制到模块中。 点击运行即可&#xff0c;然后就能提取指定目录下的所有excel文件信息并合并到一起…

连接oracle时出现ORA-12541:TNS:无监听程序的错误

遇到个问题&#xff0c;有一台windows serve 的服务器&#xff0c;这台服务器&#xff08;只部署了oracle&#xff09;忽然监听出问题了&#xff0c;提示 一、问题检查步骤&#xff1a; 1.winR--->cmd--->输入 lsnrctl status 查看监听的状态 如果监听器未运行&#…

【01】JAVASE-Java基础入门【从零开始学JAVA】

Java零基础系列课程-JavaSE基础篇 Lecture&#xff1a;波哥 Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机&#xff0c;Java 仍是企业和开发人员的首选开发平台。…

移远通信再推系列高性能卫星、5G、GNSS及三合一组合天线

4月23日&#xff0c;全球领先的物联网整体解决方案供应商移远通信正式宣布&#xff0c;再次推出多款高性能天线产品&#xff0c;以进一步满足物联网市场对高品质天线产品的需求。 其中包括卫星天线YETN001L1A、三合一组合天线YEMA300QXA和YEMN302Q1A&#xff0c;外部5G天线YECN…

windows环境下搭建Sqli-Labs靶场

目录 靶场介绍&#xff1a; 一&#xff0c;PHPstudy下载&#xff0c;安装 二&#xff0c;SQLI-LABS靶场上传 靶场介绍&#xff1a; SQLi-LABS 是一款SQL注入的靶场环境&#xff0c;共有75关&#xff0c;是网安入门、进阶必打的一款靶场&#xff1b; 下面的测试场景都支持GE…

MIGO行项目屏幕增强

MIGO行项目屏幕增强 一、增强描述 由于在事务码MIGO中存在的字段中没有能够满足客户需求的字段&#xff0c;所以需要在事务码MIGO的屏幕中添加一个新的页签用来保存物料凭证中行项目增加的字段。 通过查找BADI的程序ZDEMO_BADI,输入参数MIGO后&#xff0c;得到对应BADI为MB_M…

16 - grace数据处理 - 补充 - 读GRACE数据并进行低阶项替换

16 - grace数据处理 - 补充 - 读GRACE数据并进行低阶项替换 *0* 引言*1* 主程序分享0 引言 关于Grace模型数据的介绍可以参考文章00,数据由3家机构发布,这里做一个关于数据读取的补充,源码来自这里,直接运行slepian_delta中的程序会出现😊意想不到😊的错误,下面分享的…

虚拟化及Docker基础

一、虚拟化 1.1 云端 1.2 云计算服务模式分层 1.3 虚拟化架构 1.3.1 寄居架构 1.3.2 原生架构 1.4 虚拟化产品 1.4.1 仿真虚拟化产品&#xff08;对系统硬件没有要求&#xff0c;性能最低&#xff09; 1.4.2 半虚拟化 &#xff08;虚拟机可以使用真机物理机&#xff09…

【GitHub】github学生认证,使用copilot教程

github学生认证并使用copilot教程 写在最前面一.注册github账号1.1、注册1.2、完善你的profile 二、Github 学生认证 &#x1f308;你好呀&#xff01;我是 是Yu欸 &#x1f30c; 2024每日百字篆刻时光&#xff0c;感谢你的陪伴与支持 ~ &#x1f680; 欢迎一起踏上探险之旅&a…

详细分析MySQL中的distinct函数(附Demo)

目录 前言1. 基本知识2. 基础Demo3. 进阶Demo 前言 该函数主要用于去重&#xff0c;对于细节知识&#xff0c;此文详细补充说明 1. 基本知识 DISTINCT 是一种用于查询结果中去除重复行的关键字 在查询数据库时&#xff0c;可能会得到重复的结果行&#xff0c;但有时只需要这…

[c++]菱形继承解析

菱形继承 大概示意图&#xff1a; 菱形继承不一定只是标准的菱形&#xff0c;只要形似菱形的都可以叫菱形继承。 (以下说明都是默认公有继承&#xff0c;public和protected成员情况下) 菱形继承会造成数据的冗余和二义性&#xff1a; 冗余&#xff1a;一个Assitant对象里面有…

详细分析PyInstaller打包python为exe执行文件(附Demo)

目录 前言1. 基本知识2. Demo 前言 需要将python文件打包成exe文件&#xff0c;变成rpa自动化形式 1. 基本知识 PyInstaller是一个用于将Python应用程序打包成独立可执行文件的工具 可以将Python脚本打包成Windows、Linux和Mac OS X上的可执行文件&#xff0c;这个作用可以将…