同步操作将从 deepinwiki/wiki 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
tauri 是一个基于系统 web 组件的界面开发框架。它的优势是将前端开发的经验,搬到桌面界面开发上来。个人认为是很有前途,很经济的一种界面开发方式。
tauri 前端依赖 webview 组件,可以使用任意 js 框架,后端是 rust 程序,但是后续会增加其他语言的后端,毕竟 rust 是相对比较复杂的编程语言。
核心生态:
工具:
上游项目:
前端开发有各种框架,我这里选择 vue3 + vite 的方式。不了解前端开发的人,可能只了解一丢丢 js + html 的知识。现代前端开发,它已经不是打开一个编辑器,写html 里面的 js 那么原始(这类js 被称为 vanilla js),它已经变得非常复杂,组件化,工具化。
前端工具依赖 node.js 这个脱离浏览器可以执行 js 的引擎,它可以读写文件等操作,比在浏览器内部的 js 更加强大。所以很多前端工具本身就用 js 开发。
当然前端代码本身是不依赖 node.js 的,还是在浏览器中运行, node.js 只是构建工具。
然后是 js 的组件化。 js 开发者通过开发不同的 js 文件,共享给用户使用,这是一种最原始的组件化。现代 js 对这些 js 文件进行了组织。
import {xxx} from "xxx"; // 导入xxx 模块
导入模块会找寻绝对路径或者相对路径上的 js 文件,但是在 node.js 中,他会自动搜寻 node_modules 中的包。这是因为 node.js 支撑的构建工具,如 vite 等扩展了这个能力,它发现导入模块的时候,会自动绑定相关的 js 文件。
因此,第三方开发的组件,就通过构建工具自动关联起来,比如 yarn 这个工具,可以从网上下载相关组件,存放在 node_modules 文件夹中,进行统一管理。js 开发者不需要去管具体的文件,只需要填写正确的名称,其他由yarn 系统管理,比如组件的版本更新等等。
现代 js 组件就不需要用户自己手动下载,然后自己去匹配管理了。vite 还会在开发的时候,启动一个服务器,让代码可以随时编写,随时更新变化。最终发布的时候,vite 还会将相关的组件,关联实际的 js 文件,进行打包整合,从而成为一个静态的网站。也就是说,现代前端开发,它是高度依赖 node.js 和 vite 之类的构建工具的,而且相关的开发框架,如 vue 也是匹配这些工具来实施开发流程。只有最终发布的时候,才重新编译成传统的 js + html 源代码。
也就是说,前端开发是分成两个部分,一个是在开发阶段,vite 提供服务器和热更新,让修改和生效变得更加快捷。而 yarn(或 npm)管理相关的 js 组件的引用。等一切准备完毕,再完整的编译输出静态网站,这时候就能脱离 vite 和 nodejs 之类的工具,部署到真正的 web 服务器上了(对于 tauril 来说就是一起打包成桌面程序的安装器)。
理解现代前端开发的现状之后,下面介绍 vue 框架是怎么回事。vue 框架提供了一些抽象,标签的属性变化是响应式,页面也变成组件式,事件处理更加方便。它虚拟 dom 节点操作,让程序运行效率得到提高。
但是这点还不足以让传统桌面开发人员满意,因为我们都是习惯拖控件,写事件处理这种模式。因此还需要一个界面库,包含按钮什么的,它应该是基于 vue3 来开发的,不过相对而言,vue3 的控件库比较少,这里使用 naive-ui。
tauri 还包含后端,后端就负责一些高权限的业务,比如文件操作,前端的js 是做不到的。通过绑定后端接口,前端就能调用后端来执行一些具体的业务操作。
vanilla js (即基本 js) 最新版已经增加了不少模块化的技术,还有标准化的侦听技术,大大增强它作为动态语言的灵活性。js 语言最大的特色就是动态性,可以随意扩展属性。vue 等框架会大量使用这些特性。项目中也经常会用到 ts。ts (TypeScript) 语言是 js 的类型标注版本,它就是让变量类型更明确,静态分析的时候能提前发现类型错误,最终也会通过转换工具输出成 js。(有兴趣可以自行了解)
// 1. 类
class A{
name:'', // 数据成员
constructor(name){ this.name =name}, // 构造函数, this 绑定当前对象
toString(){console.log(name)}, // 重载原型对象函数
}
class B extends A { // 继承
constructor(){super("B")}, // 调用父类构造函数
}
// 2. 模块化
export var name = 'abc' // 导出
import { age } from 'mymodule' // 导入
// 3. 箭头函数
let add = (a,b) => a + b // add 保存了箭头函数,箭头函数会捕获外部变量a,b。并且箭头函数不会自动像成员函数那样设置 this,所以只会捕获当前环境下的this(如果使用this)。
// 4. 函数参数默认值
function foo(a= 1, b = 2){return a + b}
// 5. 模板字符串
let name = `反引号包裹${add(1,2)} ${foo()}` // 对字符串进行简易拼接
getname`a ${b} ${c}` //标签函数,参数是字符串组和对应变量的方式传递到处理函数,即 ['a ', ' '], b, c
// 6. 解构赋值
let [a, ,b] = [1, 2, 3] // a = 1, b = 3
// 7. 延展操作符
[...'hello'] // 等于 ['h','e', 'l', 'l', 'o']
add(...[1,2]) // 等于 add(1, 2)
// 8. 对象属性简写
let adder = {a, b} // 相等于 adder = { a:a, b:b}
// 9. Promise 异步对象
// Promise((f,g)=>{}) , f 产生成功结果, g 产生失败结果
// promise.then(f2, g2), f2 处理成功结果, g2 处理失败结果
// promise.catch(err=>{}) 捕获期间产生的错误
// promise 只有成功或失败且无法变更结果!谨记!一个异步任务对应一个异步对象,一个异步对象对应一个结果,但是,可以在完成上一个任务时创建新的任务(即返回一个新的异步对象),这样就达成了链式操作。
// 参数可以通过 f, g 的参数或 f2,g2 的返回值来传递(当该返回值不是 promise 时)
let p = a =>new Promise(f=>setTimeout(f, 1000, a)) // 一秒后成功
let v = a =>{console.log(a);a+=1;return p(a)} // 下一个任务也是 p()
p(6).then(v).then(v).then(v).catch(err=>console.log(err)) // 链式调用,依次输出 6 7 8,相隔一秒
// 10. let 与 const
let a = 1 // 定义于块级作用域
const b = {} //类上,常量,不能修改
b.name = "jim" // 但能扩展成员
// 11. for ... of
for (let item of 'hello'){ // 遍历任意迭代器
console.log(item)
}
// 12. Symbol
let s = Symbol('jim') // 全局唯一,jim 只是名称,无法创建相同 symbol
let p = {[s]:123} // 用于对象的键
p[s] // 123
p[Symbol.for('bob')] = 456 // Symbol.for 将会在全局注册一个 symbol
p[Symbol.for('bob')] // 获取同名 symbol,输出 456
// 13. Iterator / Generator
// 实现 Symbol.iterator 属性的对象
// 它将返回一个 {next()->{value, done}} 对象
let iter = 'hello'[Symbol.iterator]()
for (const item of iter){
console.log(item)
}
let obj = { // 自己实现迭代器接口
data: 'jim',
[Symbol.iterator]:function *(){// 生成器函数,注意星号标志
// 生成器将产生 {next()->{value,done}} 对象
for (const item of this.data) {
yield item // 暂停点,可再次调用 next() 重入
}
},
}
for (const item of obj){ // 每次循环等于调用 next() 获得 item 值
console.log(item) // 直到 next() 返回 {value, done:false}
}
// 14. Set / WeakSet
let s = new Set([1,1,2,3]) // 集合,元素不重复,即得到 Set(3){1,2,3}
// 15. Map / WeakMap
let m = new Map([]) // 键值对。因为object 的键会转换为字符串"[object Object]",所以无法区分不同对象。
let [a,b] = [{},{}]
m.set(a, 123) // 注意 m[a] 等于 m[b] 等于 m['[object Object]']
m.set(b, 456)
console.log(`a = ${m.get(a)}, b = ${m.get(b)}`)// a = 123, b = 456
// 16. Proxy / Reflect
// 代理对象是一个深度的包装,成员对象的变化也会侦测到
let person = {name:'jim', age:12}
let handler = { // 代理逻辑
get(target, prop){ // 读取属性
console.log(`get ${prop}`) // 访问代理对象将打印相关操作
// return target[prop] // 正常逻辑
Reflect.get(target, prop) // 同上,调用原始操作
},
set(target, prop, value){ // 设置属性,还包含更多可代理操作
target[prop] =value
console.log(`${prop} set ${value}`)
return true
},
}
let p = new Proxy(person, handler) // 返回代理对象
p.hello = 123 // hello set 123
p.hello // get hello
// 17. Regex
// 1. async /await
// 该语法糖增强了 Promise 的易用性,用同步代码的方式写异步代码
// async 函数返回一个 promise 对象
// await 类似暂停点,将在异步完成后继续执行
// 可并行执行的任务,用 await Promise.all([...])
async function f(a){
console.log('start')
const p1 = await (() => new Promise(f=>setTimeout(()=>{console.log("g1"); f(a++)}, 3000)))()
console.log(p1)
const p2 = await (() => new Promise(f=>setTimeout(()=>{console.log("g2"); f(a++)}, 3000)))()
console.log(p2)
return a
}
f(6).then(x=>console.log(`end...${x}`)) // g1 6 g2 7 end...8
// 效果类似以下生成器
function *f2(a){
console.log('start')
const p1 = yield (() => new Promise(f=>setTimeout(()=>{console.log("g1"); f(a++)}, 3000)))()
console.log(p1)
const p2 = yield (() => new Promise(f=>setTimeout(()=>{console.log("g2"); f(a++)}, 3000)))()
console.log(p2)
return a
}
function co(generator){ // 执行生成器
const handler = result =>{
if (result.done) return console.log(`end...${result.value}`)
result.value.then(data=>handler(generator.next(data)))
}
handler(generator.next())
}
co(f2(6))
注意生成器有两个关键字, yield 和 yield*
因此,如果你的生成器函数没有 yield ,你用 yield* 去迭代它是没有用的 如果你希望递归生成器,那么你要注意递归函数的要点
普通函数是 return 而对应生成器就是先 yield 保存序列点,然后再 return
其二、该怎么递归调用自身?
普通的递归函数只返回一个值,而迭代器的返回是一个迭代器(序列值) 因此书写递归生成器要理解这里面的差异,因为大多数递归算法,并不需要你返回一个迭代器,而只需要一两个值 所以,有两种调用,一种是按个调用(像普通函数那样),取一两个迭代值 另一种是用 yield* 生成后续的结果
其三、递归出口条件该怎么写?
特别注意,递归的出口条件是算法的根基,虽然基础条件往往很简单,但基础错了,整个递归逻辑就是狗屎 对于普通函数是返回 return 对于生成器是增加一步,用 yield 加入到序列,再返回 return 基础情况至少提供能满足递归逻辑要求的参数(个数)
其四、最终结果
普通递归只是得到一个结果 而生成器是一系列结果(也许有人需要?) 比较违反直觉的是,生成器版本性能更高。
function *fib(n){
// 定义出口条件
// 因为递归函数本质是个回环,你得在中间 break 出去
// 必须先保存两个数,因为算法后面需要n-1 n-2
if (n < 2){ //1,0 时
for (let i = n; i >= 0; i--){
yield i
}
return
}else if (n < 0){
return
}
// 递归逻辑
let g = fib(n-1) // 获取迭代器,但只返回两个值就够了
let [a, b] = [g.next().value, g.next().value]
yield a + b // 本层出口
yield a // 下一层
yield b // 再下一层(因为前面获取了,没必要再递归)
yield* g // 剩余的
}
// 请仔细对比经典递归改变的点:
function *fib(n){
// if (n < 2) return n
if (n == 1) {yield 1; yield 0; return}
if (n == 0) {yield 0; return}
// if (n < 0) return
if (n < 0) return
// return fib(n-1) + fib(n-2)
let g = fib(n-1)
let [a,b] = [g.next().value , g.next().value]
yield a + b
yield a
yield b
yield* g
return
}
vue3 是一个前端开发框架。也就是说它实现了 web 前端需要的一些基础功能,程序员基于这个框架,可以更加快速的完成项目。假如不基于框架,那么我们就需要使用原始的操作 dom 的api,一点一点的操作 dom。
特性:
yarn add vue # 添加 vue 包到当前项目
<!-- App.vue 组件分三个部分 -->
<!-- 1. 脚本 -->
<script setup>
// 该脚本类似于 vue.defineComponent({.setup() 函数}) 定义组件
// 但会对导入和返回值做一些自动化适配,模板可以自动使用定义的对象
// 1. 可以用 import 导入系统组件,第三方组件或者自定义的组件
import {reactive, ref, computed, watch, watchEffect,
onBeforeUpdate, provide,inject} from 'vue'
// 2. 响应式数据、侦听、事件处理等常用组合式 api
let num = 123
let count = ref(num) // 基本类型的响应式包装,返回{value}包装,模板解析时会自动调用 .value 解除包装
let sum = reactive ({num}) // 对象的响应式包装,返回的是 es6 Proxy 代理对象
let result = computed({ // 计算属性,缓存计算值,除非依赖的响应式数据发生变化
get(){return '结果:' + count.value}, //get
set(value){count.value = parseInt(value.match(/\d+/)[0])} // set
})
watch(count, (newCount, oldCount)=>{ // 侦听响应式数据变化,只有变化时才执行
console.log(`更新count:${newCount} -- ${oldCount}`)
})
watchEffect(()=>{console.log(`更新sum:${sum.num}`)}) //自动执行一次,自动识别上下文的响应式数据
const input = ref(null) // 保存标签上 ref 指令指定的 dom 对象
onBeforeUpdate(()=>{ // 更新前的周期钩子
if (input.value){ // 根据单双数设置 input 块是否显示
input.value.style.display = count.value % 2 ? "none" : "inline"
}
})
let onClick = event => {// 事件处理函数
count.value+=1 // 更新内部数据
sum.num+=2 // 更新内部数据
}
// 3. 组件对外接口
defineProps({ // 定义组件的标签属性,也就是入口参数
title:{type:String, default:"Demo:"} // 可配置类型和默认值,验证函数等
})
const emit = defineEmits(['demo-event']) // 自定义事件
emit('demo-event', sum) // 激活事件,并传递参数
provide("foo", sum) // 为子组件提供一个服务,它不需要通过标签层层传递
// 只需要任何层级的子组件使用 inject 获取
const bar = inject("bar") // 获取父组件链上最近的服务对象,注入本地
</script>
<!-- 2. 模板 -->
<template>
<h1 v-html="title" />
<slot name="head" :bar="bar" v-bind="$attrs">插槽默认值</slot>
<h3>双大括号可以使用 setup 脚本返回的对象:{{num}}</h3>
<button @click="onClick">事件处理加 @ 在事件前,响应式数据变化会同步到 html,即自动更新页面: {{count}}</button>
<button @click="onClick">对象响应式:{{sum.num}}</button>
<div ref="input">请设置结果:<input v-model="result" type="text" /></div>
</template>
<!-- 3. 样式 -->
<style>
</style>
<input>
标签时相当于 :value="x" @input="e=>x=e.target.value"
(根据表单标签类型变化绑定的属性和事件),简化了写法<template>
标签v-bind:['id']
: 动态指定绑定的属性vue2 使用的是选项式,即在组件对象上有相关的属性,用来分别保存数据成员,事件等等。vue3 使用的是组合式 api,将业务逻辑都封装到 setup 配置函数内。选项式分门别类各种内容,貌似更加条理清晰,易于理解。那么组合式 api 有什么优点?
组合式 api 的优点就是它可以将业务分段,然后在 setup 用引入即可。这种分段后的用户函数,也叫组合式函数 Composables。
// setup 内
useComposable1() // 用户片段,封装某种功能
useComposable2() // Composables 函数就相当于用户封装的 setup 的一部分,可以用 setup 的几乎所有功能(除几个宏),因为 setup 内都是组合式 api。 支持组合拼接,正是它最大的优势
// 最佳实践:以 use 开头命名,参数使用 ref 和返回使用 {ref...}
...
defineProps({...}) // 当前组件业务逻辑
onMounted(()=>...)
钩子函数参数:
// setup 中
// 指令目的偏向操作 dom
// 最佳实践: 不推荐在自定义组件上使用自定义指令,非要使用时组件必须是单根节点,因为最终会绑定到根上(dom)
const vMyDirective = { // 命名 v 开头
mounted:(el, binding, vnode){ // 基于各种钩子。 不指定时为在 mounted 和 updated 执行
}
}
插件为 vue 添加功能,可以说是组件,用户函数,自定义指令的集合。
const myPlugin = {
install(app, options) { // app.use(myPlugin,{}) 安装插件
app.component(...) // 注册组件
app.provide(...) // 提供服务(依赖注入)
app.directive(...) // 注册自定义指令
app.config.globalProperties // 配置全局实例属性和方法
}
}
vue 框架虽然易于理解,容易使用,但是也有一些值得思考的点,和应该注意的地方。
<!-- 左倾大 V -->
<a>
<b>
<c>
</c>
</b>
</a>
<brother> <!-- 同级化 -->
<a></a>
<b></b>
<c></c>
</brother>
<provider :use="用户函数集合"> <!-- 服务节点 -->
</provider>
<sl>
<template #header v-slot="{a,b,onClick}">
{{a}},{{b}},{{$emit('click')}}
</template>
<template #default v-slot="{a,b,c}" />
<template #footer v-slot="{a,b,c}" />
</sl>
我们可以开发自己的控件,当然最简单的是使用别人开发的控件。vue 内置一些控件,同时这里介绍第三方控件库: naive-ui。
安装 naive-ui
yarn add naive-ui
# yarn add vfonts # 相关字体
yarn add @vicons/utils # 图标工具,先在 xicons.org 查找后使用
yarn add @vicons/fa # 图表库
使用方法:
// 1. 引入
<script setup>
import {NButton, NConfigProvider, zhCN,
dateZhCN, darkTheme} from 'naive-ui'
// import 'vfonts/Lato.css' // 通用字体
// import 'vfonts/FiraCode.css' // 等宽字体
// Icon{size, color, tag} 配置单个图标
// naive-ui 也有图标组件 NIcon
// IconConfigProvider{size, color, tag} 配置默认值
import {Icon, IconConfigProvider} from @vicons/utils
/** 通过下面方式(JSDoc)可以让编辑器识别到对象结构,给出提示
* js 代码没有类型检查,不会提前报错,有点麻烦
* @type import('naive-ui').GlobalThemeOverrides
*/
const mytheme = { common: { fontWeightStrong: '600' } }
</script>
// 2. 在模板中使用
<template>
<n-config-provider :theme="darkTheme" :theme-overrides="mytheme" :locale="zhCN"
:date-locale="dateZhCN"> 配置主题,字体,中文等
<n-button>click me!</n-button> 使用控件
</n-config-provider>
</template>
<style>
</style>
<component>
tauri = 前端项目 + rust tauri 的组合。tauri 可以在前端项目中添加一个 src-tauri 的文件夹保存后端 rust 项目。前端通过 @tauri-apps/api 和后端交互,后端通过 src-tauri/tauri.conf.json 配置开启一些功能特性给前端使用。同时,也开放前端调用后端的 api 接口,和事件驱动接口,这样,原本的前端项目就能够具备桌面程序开发的能力。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。