从Rust语言学习经验,并应用到前端JS中
前言
Rust 是当下最热门、最先进的语言之一,前端领域的新三剑客更被戏称为"js, ts, rs",不过 rust 在前端领域更多的是参与到基础建设(swc 等)、wasm(yew 等)等方面,而 wasm 实际上在大多数场景下性能都不如拥有 JIT 的 js,所以主要场景就只剩下了基础建设
那么对于我们这些日常工作中不常接触基础建设的纯前端人来说,能从 rust 中学习到一些什么东西呢,这就是本文所关注的
变量默认不可变
不像其他大多数语言,rust 放弃使用 readonly / const 之类的修饰符来表示变量不可变,而是选择了变量默认不可变,使用 mut 修饰符来显示表示变量是可变的。这强迫开发者必须思考这个变量是不是真的需要变化,牺牲了灵活性获得了更好的可维护性
let a = 1;
a = 2; // error!
let mut b = 1;
b = 2; // right
随着语言的发展,mutable 越来越被认为是有害的,幸运的是,js 也可以很方便地声明不可变变量,所以我们的变量也应该默认是 const 的(eslint 可以检查)
依赖类型系统构建更安全的代码
开启严格模式
相较于 ts,rust 的类型推导能力更加强大
fn main() {
// 因为有类型说明,编译器知道 `elem` 的类型是 u8。
let elem = 5u8;
// 创建一个空向量 vector(即不定长的,可以增长的数组)。
let mut vec = Vec::new();
// 这时候,vec 是 Vec<T>
vec.push(elem);
// 这时候,vec 变成了 Vec<u8>
vec.push("s") // error, expected `u8`, found `&str`
}
而 ts 没有这个能力,导致有些时候我们的代码不能得到类型系统的保护,开启严格模式可以显著减少这类问题,比如:
// 非严格模式
const [state, setState] = useState([]);
^^^ state is any[]
setState([1])
setState(["xxx"]) // state 可以被赋予任意值
// 严格模式
const [state, setState] = useState([]);
^^^ state is never[]
setState([1]) // error
// 必须提前定义类型
const [state, setState] = useState<number[]>([]);
setState([1]) // right
setState(["xxx"]) // error
更安全的异常处理
在有些前端代码中充斥着各种 try catch,这是一种非常低效且不安全的错误处理机制,这里不展开讨论。我们看看 rust 是怎么做的
在绝大多数情况下,rust 要求你必须承认你的代码是可能出错的,并提前采取行动来处理这些错误。错误分为两类:可恢复错误和不可恢复错误。rust 用 Result<T, E> 处理可恢复错误,用 panic! 处理不可恢复错误。这里我们不讨论异常处理的哲学,只看可恢复错误的处理方式
// 背景知识,rust 内置了 Result 枚举,没有错误时返回 Ok(T),错误时返回 Err(E)
enum Result<T, E> {
Ok(T),
Err(E),
}
// 编写我们的业务逻辑
fn may_fail() -> Result<Happy, Sad> {
// 业务逻辑
}
// 编写一个 wrap 函数统一处理错误
fn call_and_handle() -> Result<(), ()> {
match may_fail() {
Ok(happy) => {
println!(":)");
Ok(())
},
Err(sad) => {
eprintln!(":(");
// 在这里编写统一的错误处理逻辑
Err(())
}
}
}
fn caller() -> Result<(), ()> {
call_and_handle()?; // ? 是一个宏,在 Err 发生时终止函数的执行
call_and_handle()?;
call_and_handle()?;
// 除了 match,还可以用 if let 更快捷地处理某些场景
if let Ok(happy) = may_fail() {
println!("Ok! {:?}", happ);
} else {
// 解构失败。切换到失败情形。
println!("some thing error!");
};
println!("I am so happy right now");
Ok(())
}
fn main() -> Result<(), ()> {
caller()?;
caller()?;
caller()
}
可以看到,核心的处理思想是:错误链是可传播的,一旦一个地方定义了可能发生的错误,那么任意引用的地方都得考虑错误情况,否则编译就会报错,所谓错误,仅仅是代码逻辑的另一个分支罢了
ts 同样可以做到这点:
// 一个简单封装的 axios 请求
export interface CustomAxiosResponse<T> {
success: true;
msg?: string;
data: T;
}
export interface CustomAxiosResponseErr {
success: false;
code: string;
msg: string;
isHttpError: boolean;
}
export const request = async <T>(
config: RequestConfig,
): Promise<CustomAxiosResponse<T> | CustomAxiosResponseErr> => {
try {
const response = await axios.create({}).request(config);
if (response.data.code !== '000000') {
return {
success: false,
code: response.data.code,
msg: response.data.msg,
isHttpError: false,
};
}
return {
success: true,
data: response.data.data,
};
} catch (e) {
return {
success: false,
code: '',
msg: '',
isHttpError: !isNumber(Number(e)),
};
}
};
// 一个获取用户信息的接口
export const getInfo = () =>
request<UserDetail>({
url: '/info',
method: 'get',
});
// 使用
async () => {
const res = await getInfo();
const { name } = res.data;
^^^ error, 请求可能失败
if (!res.success) {
// do something
return;
}
const { name } = res.data // right,因为上面已经处理了错误的分支
}
或者更进一步,我们可以舍弃 if else,也采用 rust 中的 Result 和 Option 结构,就像这个库做的
什么是字符串?
看起来是个很弱智的问题,在大多数语言中,字符串就是字符串,像 js 这样的脚本语言对字符串的定义更是简单——字符串就是文本片段。但是事实上,字符串可能比我们印象中更复杂。
字面量(literal)
一个容易被忽视的点是,我们写下的代码和真正运行的代码并不完全一样,中间是有一次转义的,即使大多数情况下这次转义看起来无关紧要
// 我们用引号包裹了一段文本,这就是 js 中的字符串了
// 但是更精确的说,'something' 是一段字符串字面量,但此时字面量的值和真实的变量值是相同的
const str = 'something'
// 但是某些情况下,字面量的值不再和变量值相同
const str2 = 'sss\u2714'
^^^ str2 is 'sss✔'
Rust 中的字符串
rust 中的字符串类型更复杂,主要可以分为 3 类:String、str、&str
str 是语言级别唯一的一种字符串类型,被称为字符串切片
&str 是 str 的引用,通常我们不会直接接触到 str,接触到的都是其引用
String 是标准库实现的字符串类型,相较于 str 拥有更多更灵活的能力。str 硬编码的、无法修改的,而 String 是可变长度的、可改变所有权的 UTF-8 编码字符串
// 字符串字面量是字符串切片
let myStr = "something";
// 相当于
let myStr: &'static str = "something"; // 'static 是生命周期,表示整个周期内有效
// 也就是说,我们写下的 "something" 是 str
// 而被赋值的变量 myStr 是对其的引用,类型是 &str
let mut myString = String::from("something"); // 或者 "something".to_string()
// 可以修改
myString.push('x');
那么,这有什么用
对于 js 这种不需要关心内存分配的脚本语言来说,似乎是“没用的知识又增加了”,不过有时候他也能帮助我们理解一些不那么直观的设计背后的原因。
看到其他文章有提到 webpack.DefinePlugin 中的字符串值必须用 JSON.stringify 或者更多一层的引号包裹起来才能正常使用,官方文档也明确说明了这一点,但是并没有详细解释原因,如果我们了解字面量的概念的话其实就很容易理解了
webpack plugin 是依赖 parser.hooks 工作的,而 parser 是依赖 ast 工作的,试想一个最简单的场景:
// 我们定义了一个全局变量
new webpack.DefinePlugin({
VERSION: "5fa3b9"
})
// 代码中使用
const a = VERSION
// 那么对于 VERSION,AST 中会解析成如下结构
{
"type": "Identifier",
"name": "VERSION",
...
}
// 替换之后
{
"type": "Identifier",
"name": "5fa3b9",
...
}
// 显然,当我们再次解析 AST 生成编译代码后, 5fa3b9 变成了一个变量而不是字符串,这时候就会报错
你可能会问,为什么替换过程中不自动处理成 "name": "'5fa3b9'"
呢,因为这里只是最简单的 Identifier 场景,事实上还有很多个其他场景不易处理。或者换个角度想,如果真有默认处理的逻辑,那我希望把 VERSION 替换为 window.version
,就无法实现了
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanegjj
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
photoshop蒙版画笔没反应怎么办
PHP中文网 06-24