草庐IT

Rust编程语言入门之cargo、crates.io

小乔的博客 2023-04-13 原文

cargo、crates.io

本章内容

  • 通过 release profile 来自定义构建
  • https://crates.io/上发布库
  • 通过 workspaces 组织大工程
  • https://crates.io/来安装库
  • 使用自定义命令扩展 cargo

一、通过 release profile 来自定义构建

release profile (发布配置)

  • release profile:
    • 是预定义的
    • 可自定义:可使用不同的配置,对代码编译拥有更多的控制
  • 每个 profile 的配置都独立于其它的 profile
  • cargo 主要的两个 profile:
    • dev profile:适用于开发,cargo build
    • release profile:适用于发布,cargo build --release

自定义 profile

  • 针对每个 profile,Cargo 都提供了默认的配置
  • 如果想自定义 xxxx profile 的配置:
    • 可以在 Cargo.toml 里添加 [profile.xxxx] 区域,在里面覆盖默认配置的子集
[package]
name = "closure"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

[profile.dev]
opt-level = 1

[profile.release]
opt-level = 3

执行

closure on  master [?] is ? 0.1.0 via ? 1.67.1 took 2.9s 
➜ cargo build
   Compiling closure v0.1.0 (/Users/qiaopengjun/rust/closure)
    Finished dev [optimized + debuginfo] target(s) in 0.16s

closure on  master [?] is ? 0.1.0 via ? 1.67.1 
➜ 

二、发布 crate 到 crates.io (1)

Crates.io

  • 可以通过发布包来共享你的代码
  • crate 的注册表在 https://crates.io/
    • 它会分发已注册的包的源代码
    • 主要托管开源的代码

文档注释

  • 文档注释:用于生成文档
    • 生成 HTML 文档
    • 显式公共 API 的文档注释:如何使用API
    • 使用 ///
    • 支持 Markdown
    • 放置在被说明条目之前

生成 HTML 文档的命令

  • cargo doc
    • 它会运行 rustdoc 工具(Rust 安装包自带)
    • 把生成的 HTML 文档放在 target/doc 目录下
  • cargo doc --open:
    • 构建当前crate的文档(也包含 crate 依赖项的文档)
    • 在浏览器打开文档
/// Adds one to the number given.
/// 
/// # Examples
/// 
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
/// 
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
    x + 1
}

运行

closure on  master [?] is ? 0.1.0 via ? 1.67.1 
➜ cargo doc  
 Documenting closure v0.1.0 (/Users/qiaopengjun/rust/closure)
    Finished dev [optimized + debuginfo] target(s) in 0.39s

closure on  master [?] is ? 0.1.0 via ? 1.67.1 
➜ cargo doc --open
    Finished dev [optimized + debuginfo] target(s) in 0.00s
     Opening /Users/qiaopengjun/rust/closure/target/doc/closure/index.html

closure on  master [?] is ? 0.1.0 via ? 1.67.1 
➜ cargo doc --open
 Documenting closure v0.1.0 (/Users/qiaopengjun/rust/closure)
    Finished dev [optimized + debuginfo] target(s) in 0.41s
     Opening /Users/qiaopengjun/rust/closure/target/doc/closure/index.html

closure on  master [?] is ? 0.1.0 via ? 1.67.1 

常用章节

  • # Examples
  • 其它常用的章节:
    • Panics:函数可能发生 panic 的场景
    • Errors:如果函数返回 Result,描述可能的错误种类,以及可导致错误的条件
    • Safety:如果函数处于 unsafe 调用,就应该解释函数 unsafe 的原因,以及调用者确保的使用前提。

文档注释作为测试

  • 示例代码块的附加值:
    • 运行 cargo test:将把文档注释中的示例代码作为测试来运行
/// Adds one to the number given.
/// 
/// # Examples
/// 
/// ```
/// let arg = 5;
/// let answer = closure::add_one(arg);
/// 
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
    x + 1
}

为包含注释的项添加文档注释

  • 符号://!
  • 这类注释通常用描述 crate 和模块:
    • crate root(按惯例 src/lib.rs)
    • 一个模块内,将 crate 或模块作为一个整体进行记录
//! # Closure Crate
//! 
//! `closure` is a collection of utilities to make performance
//! calculations more convenient.

/// Adds one to the number given.
/// 
/// # Examples
/// 
/// ```
/// let arg = 5;
/// let answer = closure::add_one(arg);
/// 
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
    x + 1
}

三、pub use

使用 pub use 导出方便使用的公共 API

  • 问题:crate 的程序结构在开发时对于开发者很合理,但对于它的使用者不够方便
    • 开发者会把程序结构分为很多层,使用者想找到这种深层结构中的某个类型很费劲
  • 例如:
    • 麻烦:my_crate::some_module::another_module::UsefulType;
    • 方便:my_crate::UsefulType;
  • 解决办法:
    • 不需要重新组织内部代码结构
    • 使用pub use:可以重新导出,创建一个与内部私有结构不同的对外公共结构
//! # Art
//! 
//! A library for modeling artistic concepts.

pub mod kinds {
    /// The primary colors according to the RYB color model.
    pub enum PrimaryColor {
        Red,
        Yellow,
        Blue,
    }

    /// The secondary colors according to the RYB color model.
    pub enum SecondaryColor {
        Orange,
        Green,
        Purple,
    }
}

pub mod utils {
    use crate::kinds::*;

    /// Combines two primary colors in equal amounts to create
    /// a secondary color.
    pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
        SecondaryColor::Green
    }
}

src/main.rs 文件

use art::kinds::PrimaryColor;
use art::utils::mix;

fn main() {
    let red = PrimaryColor::Red;
    let yellow = PrimaryColor::Yellow;
    mix(red, yellow);
}

优化之后:

src/lib.rs 文件

//! # Art
//! 
//! A library for modeling artistic concepts.

pub use self::kinds::PrimaryColor;
pub use self::kinds::SecondaryColor;
pub use self::utils::mix;

pub mod kinds {
    /// The primary colors according to the RYB color model.
    pub enum PrimaryColor {
        Red,
        Yellow,
        Blue,
    }

    /// The secondary colors according to the RYB color model.
    pub enum SecondaryColor {
        Orange,
        Green,
        Purple,
    }
}

pub mod utils {
    use crate::kinds::*;

    /// Combines two primary colors in equal amounts to create
    /// a secondary color.
    pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
        SecondaryColor::Green
    }
}

src/main.rs 文件

// use art::kinds::PrimaryColor;
// use art::utils::mix;
use art::PrimaryColor;
use art::mix;

fn main() {
    let red = PrimaryColor::Red;
    let yellow = PrimaryColor::Yellow;
    mix(red, yellow);
}

四、发布 crate 到 crates.io(2)

创建并设置 Crates.io 账号

  • 发布 crate 前,需要在 crates.io 创建账号并获得 API token
  • 运行命令:cargo login [你的 API token]
    • 通知 cargo,你的 API token 存储在本地 ~/.cargo/credentials
  • API token 可以在https://crates.io/进行撤销

为新的crate 添加元数据

  • 在发布crate之前,需要在 Cargo.toml 的 [package] 区域为 crate 添加一些元数据:
    • crate 需要唯一的名称:name
    • description:一两句话即可,会出现在 crate 搜索的结果里
    • license:需提供许可证标识值(可到 http://spdx.org/licenses/查找)
      • 可指定多个 license:用 OR
    • version
    • author
  • 发布:cargo publish 命令
rust_tutorials on  master [?] via ? 1.67.1 
➜ cargo login --registry crates-io               
please paste the token found on https://crates.io/me below
ciopLk54SDAxB200gA4jk85abcdefgabcabc # token
       Login token for `crates-io` saved

rust_tutorials on  master [?] via ? 1.67.1 took 1m 27.6s 
➜ 

rust_tutorials on  master [?] via ? 1.67.1 took 2.0s 
➜ cargo publish --registry crates-io --allow-dirty
    Updating crates.io index

发布到 Crates.io

  • Crate 一旦发布,就是永久性的:该版本无法覆盖,代码无法删除
    • 目的:依赖于该版本的项目可继续正常工作

发布已存在 crate 的新版本

  • 修改 crate 后,需要先修改 Cargo.toml 里面的version 值,再进行重新发布
  • 参照http://semver.org/来使用你的语义版本
  • 再执行 cargo publish 进行发布

使用 cargo yank 从 Crates.io 撤回版本

  • 不可以删除 crate 之前的版本
  • 但可以防止其它项目把它作为新的依赖:yank(撤回)一个 crate 版本
    • 防止新项目依赖于该版本
    • 已经存在项目可继续将其作为依赖(并可下载)
  • yank 意味着:
    • 所有已经产生 Cargo.lock 的项目都不会中断
    • 任何将来生成的 Cargo.lock 文件都不会使用被 yank 的版本
  • 命令:
    • yank 一个版本(不会删除任何代码):cargo yank --vers 1.0.1
    • 取消 yank:cargo yank --vers 1.0.1 --undo

五、Cargo 工作空间

Cargo 工作空间(Workspaces)

  • cargo 工作空间:帮助管理多个相互关联且需要协同开发的 crate
  • cargo 工作空间是一套共享同一个 Cargo.lock 和输出文件夹的包

创建工作空间

  • 有多种方式来组建工作空间例:1个二进制 crate,2个库 crate
    • 二进制 crate:main函数,依赖于其它2个crate
    • 其中1个库crate 提供 add_one 函数
    • 另外1个库crate 提供 add_two 函数
~/rust
➜ mcd add  # mkdir add cd add

~/rust/add
➜ touch Cargo.toml

~/rust/add via ? 1.67.1
➜ c  # code .

~/rust/add via ? 1.67.1
➜

~/rust/add via ? 1.67.1
➜ cargo new adder
warning: compiling this new package may not work due to invalid workspace configuration

current package believes it's in a workspace when it's not:
current:   /Users/qiaopengjun/rust/add/adder/Cargo.toml
workspace: /Users/qiaopengjun/rust/add/Cargo.toml

this may be fixable by adding `adder` to the `workspace.members` array of the manifest located at: /Users/qiaopengjun/rust/add/Cargo.toml
Alternatively, to keep it out of the workspace, add the package to the `workspace.exclude` array, or add an empty `[workspace]` table to the package's manifest.
     Created binary (application) `adder` package

~/rust/add via ? 1.67.1
➜ cargo build
   Compiling adder v0.1.0 (/Users/qiaopengjun/rust/add/adder)
    Finished dev [unoptimized + debuginfo] target(s) in 0.48s

~/rust/add via ? 1.67.1

~/rust/add via ? 1.67.1
➜ cargo new add-one --lib
     Created library `add-one` package

~/rust/add via ? 1.67.1
➜

add-one/src/lib.rs 文件

pub fn add_one(x: i32) -> i32 {
    x + 1
}

adder/src/main.rs 文件

use add_one;

fn main() {
    let num = 10;
    println!("Hello, world! {} plus one is {}", num, add_one::add_one(num));
}

adder/Cargo.toml 文件

[package]
name = "adder"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

add-one = { path = "../add-one" }

rust/add/Cargo.toml 文件

[workspace]

members = [
    "adder",
    "add-one",
]

运行

~/rust/add via ? 1.67.1 
➜ cargo build            
   Compiling add-one v0.1.0 (/Users/qiaopengjun/rust/add/add-one)
   Compiling adder v0.1.0 (/Users/qiaopengjun/rust/add/adder)
    Finished dev [unoptimized + debuginfo] target(s) in 0.30s

~/rust/add via ? 1.67.1 
➜ cargo run -p adder                
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/adder`
Hello, world! 10 plus one is 11

~/rust/add via ? 1.67.1 
➜ 

在工作空间中依赖外部 crate

  • 工作空间只有一个 Cargo.lock 文件,在工作空间的顶层目录
    • 保证工作空间内所有 crate 使用的依赖的版本都相同
    • 工作空间内所有 crate 相互兼容

为工作空间添加测试

rust/add/add-one/src/lib.rs 文件

pub fn add_one(x: i32) -> i32 {
    x + 1
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn it_works() {
        assert_eq!(3, add_one(2));
    }
}

执行测试

~/rust/add via ? 1.67.1 
➜ cargo test        
   Compiling add-one v0.1.0 (/Users/qiaopengjun/rust/add/add-one)
   Compiling adder v0.1.0 (/Users/qiaopengjun/rust/add/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.13s
     Running unittests src/lib.rs (target/debug/deps/add_one-cb079acb8d173784)

running 1 test
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/adder-23a2e001f7410351)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests add-one

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s


~/rust/add via ? 1.67.1 
➜ 

~/rust/add via ? 1.67.1 
➜ cargo test -p add-one                      
    Finished test [unoptimized + debuginfo] target(s) in 0.00s
     Running unittests src/lib.rs (target/debug/deps/add_one-cb079acb8d173784)

running 1 test
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests add-one

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s


~/rust/add via ? 1.67.1 
➜ 

六、从 CRATES.IO 安装二进制 crate

从 CRATES.IO 安装二进制 crate

  • 命令:cargo install
  • 来源:https://crates.io
  • 限制:只能安装具有二进制目标(binary target)的 crate
  • 二进制目标 binary target:是一个可运行程序
    • 由拥有 src/main.rs 或其它被指定为二进制文件的 crate 生成
  • 通常:README 里有关于 crate 的描述:
    • 拥有 library target
    • 拥有 binary target
    • 两者兼备

cargo install

  • cargo install 安装的二进制存放在根目录的 bin 文件夹
  • 如果你用 rustup 安装的 Rust,没有任何自定义配置,那么二进制存放目录是 $HOME/.cargo/bin
    • 要确保该目录在环境变量 $PATH 中
~/rust took 2m 8.1s
➜ cargo install rust_tutorials_qiao
    Updating `tuna` index
  Downloaded rust_tutorials_qiao v0.1.0 (registry `tuna`)
  Downloaded 1 crate (759 B) in 3.43s
  Installing rust_tutorials_qiao v0.1.0
   Compiling rust_tutorials_qiao v0.1.0
    Finished release [optimized] target(s) in 3.99s
  Installing /Users/qiaopengjun/.cargo/bin/rust_tutorials_qiao
   Installed package `rust_tutorials_qiao v0.1.0` (executable `rust_tutorials_qiao`)

~/rust took 4.0s
➜
➜ rust_tutorials_qiao
Hello, world!

~/rust took 3.1s
➜
~/rust took 3.1s
➜ echo $PATH  # 查看PATH环境变量

使用自定义命令扩展 cargo

  • cargo 被设计成可以使用子命令来扩展
  • 例:如果 $PATH 中的某个二进制是 cargo-something,你可以像子命令一样运行:
    • cargo something
  • 类似这样的自定义命令可以通过该命令列出:cargo --list
  • 优点:可使用 cargo install 来安装扩展,像内置工具一样来运行
➜ cargo --list
Installed Commands:
    add                  Add dependencies to a Cargo.toml manifest file
    b                    alias: build
    bench                Execute all benchmarks of a local package
    build                Compile a local package and all of its dependencies
    c                    alias: check
    check                Check a local package and all of its dependencies for errors
    clean                Remove artifacts that cargo has generated in the past
    clippy               Checks a package to catch common mistakes and improve your Rust code.
    config               Inspect configuration values
    d                    alias: doc
    doc                  Build a package's documentation
    fetch                Fetch dependencies of a package from the network
    fix                  Automatically fix lint warnings reported by rustc
    fmt                  Formats all bin and lib files of the current crate using rustfmt.
    generate-lockfile    Generate the lockfile for a package
    git-checkout         This command has been removed
    help                 Displays help for a cargo subcommand
    init                 Create a new cargo package in an existing directory
    install              Install a Rust binary. Default location is $HOME/.cargo/bin
    locate-project       Print a JSON representation of a Cargo.toml file's location
    login                Save an api token from the registry locally. If token is not specified, it will be read from stdin.
    logout               Remove an API token from the registry locally
    metadata             Output the resolved dependencies of a package, the concrete used versions including overrides, in machine-readable format
    miri
    new                  Create a new cargo package at <path>
    owner                Manage the owners of a crate on the registry
    package              Assemble the local package into a distributable tarball
    pkgid                Print a fully qualified package specification
    publish              Upload a package to the registry
    r                    alias: run
    read-manifest        Print a JSON representation of a Cargo.toml manifest.
    remove               Remove dependencies from a Cargo.toml manifest file
    report               Generate and display various kinds of reports
    rm                   alias: remove
    run                  Run a binary or example of the local package
    rustc                Compile a package, and pass extra options to the compiler
    rustdoc              Build a package's documentation, using specified custom flags.
    search               Search packages in crates.io
    t                    alias: test
    test                 Execute all unit and integration tests and build examples of a local package
    tree                 Display a tree visualization of a dependency graph
    uninstall            Remove a Rust binary
    update               Update dependencies as recorded in the local lock file
    vendor               Vendor all dependencies for a project locally
    verify-project       Check correctness of crate manifest
    version              Show version information
    yank                 Remove a pushed crate from the index

~/rust

有关Rust编程语言入门之cargo、crates.io的更多相关文章

  1. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  2. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  3. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  4. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  5. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  6. 7个大一C语言必学的程序 / C语言经典代码大全 - 2

    嗨~大家好,这里是可莉!今天给大家带来的是7个C语言的经典基础代码~那一起往下看下去把【程序一】打印100到200之间的素数#includeintmain(){ inti; for(i=100;i 【程序二】输出乘法口诀表#includeintmain(){inti;for(i=1;i 【程序三】判断1000年---2000年之间的闰年#includeintmain(){intyear;for(year=1000;year 【程序四】给定两个整形变量的值,将两个值的内容进行交换。这里提供两种方法来进行交换,第一种为创建临时变量来进行交换,第二种是不创建临时变量而直接进行交换。1.创建临时变量来

  7. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  8. 网络编程套接字 - 2

    网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识

  9. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  10. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

随机推荐