useCallback
和 useMemo
是 React 函数组件开发中非常重要的两个函数, 分别用于缓存方法和变量, 可以避免不必要的渲染, 提升性能。
React 函数组件就是一个 TypeScript/JavaScript 函数,当组件状态变化时, React 会重新调用这个函数进行渲染, 如果不使用缓存的话, 会导致函数组件內定义的函数和变量都会被重新定义和初始化, 从而导致错误的更新和渲染。
比如下面的代码:
import { useEffect, useState } from 'react';
export function MyComp() {
const [data, setData] = useState('');
// 注意: 每次都会重新定义 loadData 导致循环渲染;
const loadData = () => {
setData(Date.now().toString());
};
useEffect(() => {
loadData();
}, [loadData]);
return (
<div>{data}</div>
);
}
这样的代码看起来非常的符合 React 的规范, 但是实际运行起来, 就会无限循环下去。 因为每次调用 MyComp
这个组件, 都会重新定义 loadData
这个函数, 从而导致循环更新。
而使用 useCallback
来缓存 loadData
方法, 就可以避免这种情况:
import { useCallback, useEffect, useState } from 'react';
export function MyComp() {
const [data, setData] = useState('');
// 使用 `useCallback` 缓存 loadData , 避免循环渲染;
const loadData = useCallback(() => {
setData(Date.now().toString());
}, []);
useEffect(() => {
loadData();
}, [loadData]);
return (
<div>{data}</div>
);
}
useCallback
缓存的是函数定义, 即使在 React 函数组件在多次渲染时传入了新的函数, 只要依赖数组不变, 返回函数定义依然是旧的函数定义, 调用函数返回的函数, 依然会得到旧的结果。
useCallback
的 TypeScript 定义为:
function useCallback<T extends Function>(callback: T, deps: DependencyList): T;
如果
useCallback
的依赖数组项为空数组, 将会返回一个固定的函数, 那么就应该考虑将这个函数移至组件之外了。
useMemo
缓存的是的结果, 传入的计算函数不能有参数, 必须有返回值。 即使在 React 函数组件在多次渲染时传入了新的计算函数, 只要依赖数组不变, 返回的依然是旧的结果。
function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T;
如果
useMemo
的依赖数组为空数组, 将返回一个不变的常量, 则和useRef
似乎又有了一些类似。
useCallback
缓存的是函数本身, 而 useMemo
返回的则是函数的结果;useCallback
的函数参数可以是有参数的函数, 而 useMemo
的函数参数则不能有参数;useCallback
的函数参数对返回值不做要求, 而 useMemo
的函数参数则必须有返回值;useEffect
和 useLayoutEffect
函数进行同步。
吐槽一点, Effect 虽然是被用作同步的, 但是 Effect 本身的字面意思和同步没有任何关系, 导致很多中文文档中都翻译成
效应
,副作用
之类的, 感觉有点儿不伦不类, 至少和同步不搭边。 这一点, React 官方也没有一个说法, 至今没有一个信达雅
的中文翻译。
useEffect
的函数定义是这样的:
function useEffect(
effect: () => (void | Destructor),
deps?: ReadonlyArray<unknown>
): void;
我的理解是:
unknown
;常见用法有:
useEffect
的依赖项为空数组, 相当于和函数组件的生命周期进行同步, 即当控件加载之后和销毁时执行同步;
useEffect(() => {
// 当控件加载完成时执行
function onClick() {
console.log('click');
}
document.body.addEventListener('click', onClick);
// 返回一个清理函数, 在控件销毁时执行
return () => {
document.body.removeEventListener('click', onClick);
};
}, []);
useEffect
的依赖项不为空, 任意依赖项发生变化时, 会自动执行同步;
useEffect(() => {
// 当 state1 或者 state2 发生变化时, 会执行同步, 调用这个函数;
console.log(`effect for {state1: ${state1}, state2: ${state2}}`);
// 返回一个清理函数, 再次变化之前, 会调用这个清理函数;
return () => {
console.log(`cleanup for {state1: ${state1}, state2: ${state2}}`);
};
}, [state1, state2]);
useLayoutEffect
是 useEffect
的另一个版本, 在浏览器重绘界面之前调用, 用法和 useEffect
一样, 但是会影响性能。
useLayoutEffect
典型的用法是在浏览器重绘之前测量布局。
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0); // 现在不知道真正的高度
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height); // 计算得到高度
}, []);
// ...使用计算得到的高度进行后面的渲染工作 ...
}
除非有必要, 请尽可能地使用
useEffect
。
个人觉得, 一个 Effect 应当只做一件事情, 不要在一个 Effect 中做太多事情, 可以使用多个 Effect 。
不建议的用法:
useEffect(() => {
doSomethingA();
doSomethingB();
doSomethingC();
}, []);
建议的用法, 因为这样不仅更加清晰, 而且更加有利于在重构时提取控件。
useEffect(() => {
doSomethingA();
}, []);
useEffect(() => {
doSomethingB();
}, []);
useEffect(() => {
doSomethingC();
}, []);
想法是好的, 然而没想到的是现在 (2023 年 3 月) 微信的 Webview 的版本居然还是 Chrome/86 , 而当前主流的版本已经是 Chrome/110 了, 差了 20 多个版本, 于是忍不住在 V2EX 发了一片吐槽的帖子: 2023 年又发现了一个 “IE6” ,那就是安卓版微信内置的 Webview , 得到了好多网友的回复。
更加没想到的是, 支付宝/钉钉等阿里系软件内嵌的 Webview 居然更低的 Chrome/69 , 这么一比较, 微信的 Chrome/86 还算比较新的了。
吐槽完了, 活儿还是要做的, 只能自己去适配微信内置的 Chrome/86 版本的 Webview 。
由于一直是桌面端的 Web 开发, 所以就使用了一些比较新的 ES 标准, 在主流的浏览器 (Chrome/108+) 上没有任何问题, 突然要针对微信的 Chrome/86 做开发, 遇到主要问题是:
更多差异请参考这里的比较 https://caniuse.com/?compare=chrome+87,chrome+110&compareCats=all
最主要的问题是不支持 Import maps
, 就不能很好的使用 ES 模块化的代码, 但是也不能降级, 因为项目中大量用到了 import.meta.url
, 比如自带样式的组件, 降级的话又得想办法实现这个, 相当于自己又给自己挖了个不知深浅的坑, 看起来似乎无解了。
不过, 不得不感谢伟大的 Google 和 GitHub , 在一筹莫展之际, 我发现了 ES Module Shims 这个库, 可在所有支持基本ES模块的浏览器中使用, 通过它可以为 Chrome/61
以上的浏览器提供几乎完整的 ES 模块化支持, 这简直就是我的大救星啊, 虽然性能肯定会比原生差一些, 但是至少能用啊 !!!
剩下的问题都容易解决:
Top level await
不支持, 需要浏览器内核支持, 这个可以改;structuredClone
不支持, 这个可以用 JSON 序列化再反序列化顶着用;最终, 借助于 es-module-shims
, 终于在微信的 Chrome/86 的 Webview 中运行起来了。 后续肯定还有很多坑要填, 至少现在可以起步了。
前面提到的吐槽贴也在几天之后得到了一个算是半官方的回复:
LeRuin: 安卓微信 107 很快就会全覆盖的,如果你是小程序开发者,可以优先体验最新版本内核,下个版本也已经在准备了。 https://www.v2ex.com/t/918931#r_12781080
不过, 遗憾的是, 就算是微信更新到了 Chrome/107 , 也不支持 Small, Large, and Dynamic viewport units 。
]]>在我看来, React 的优势主要有:
生态好
主流的 IDE , 不管是 VSCode 及其衍生产品, 还是 JetBrains 家族的 IDE 及其衍生产品, 都内置 React 支持, 而 Angular 和 Vue 则都需要安装插件才行。TypeScript 的转译器 tsc
能直接处理 React 的 tsx/jsx
文件, 现在流行的前端 ES6 转译器 esbuild 也能直接处理 tsx/jsx
文件, 虽然 React 官方使用的是 babel 作为转译器, 但是可以有更多的选择。
入门容易, 学习曲线平缓, 几乎没有上限
React 的入门非常简单, 不需要像 Angular/Vue 那样去记忆模板语法和指令, 特别是有了 React Hooks 之后, 只需要熟悉几个函数就可以开始 React 开发了, 入门非常容易。 如果以后需要高级的功能, 就再学习几个函数, 就可以继续晋级。 最终的上限, 可能就是开发者的上限了。 这大概也是 React 的高级玩法非常多的原因吧。
贴近原生 JavaScript
网上流传这种说法, 用 React 开发感觉是在用原生 JavaScript 开发。 我 React 用的不算多, 已经有这样的感觉。
当然, 上面只是我个人的一些粗浅的看法, 接下来就正式开始介绍本文的内容。
函数组件其实一直存在于 React 中, 在 React 16.8 出现之后, 函数组件的功能到了增强, 提出了 Hooks 的概念。 一个函数函数组件就是一个函数:
import { createElement } from 'react';
export function Hello() {
return createElement(
'div',
{ className: 'card', children: 'Hello, world!' }
);
}
函数组件可以有参数, 给上面的函数组件加上参数:
import { createElement } from 'react';
export function Hello(props: HelloProps) {
return createElement(
'div',
{ className: 'card', children: `Hello, ${props.message} !` }
);
}
export interface HelloProps {
message: string;
}
如果组件的界面比较复杂时, 调用 createElement
会显得非常复杂, 因此需要采用表达界面更加清晰的 jsx/tsx
格式。
import { createElement } from 'react';
export function Hello(props: HelloProps) {
return (
<div className='card'>
Hello, {props.message} !
</div>
)
}
export interface HelloProps {
message: string;
}
组件页面要和用户进行交互, 因此需要保持状态, 函数组件使用 useState
函数来保持状态, 以 React 官方的教程中的 LikeButton
为例:
import { useState } from 'react';
export function LikeButton() {
const [liked, setLiked] = useState(false);
if (liked) {
return 'You liked this.'
}
return (
<button className='btn m-2' onClick={() => setLiked(true)}>
Like
</button>
);
}
useState
返回一个数组, 数组的第一个元素是当前的状态, 第二个是是改变状态的函数, 调用这个函数时, 会改变状态值, 同时会触发函数组件重新渲染, 函数组件根据不同的状态返回相应的界面元素,然后 React 再根据返回元素和上次的差异进行渲染。
上面的 setLiked
其实有两种用法:
// 直接传入新的状态值
setLiked(false);
// 新的状态值依赖旧的状态值时,可以类似这样调用, prev 表示旧的状态
setLiked(prev => {
return !prev;
});
在上面的例子中, 直接修改
liked
的值没有任何作用, 只有调用setLiked
方法才会改变liked
的指, 并出发函数组件的重新渲染, React 就是这种半自动更新的机制。
wpa_supplicant-2.10
导致的 (https://blog.incompetent.me/2022/07/27/workaround-ubuntu-22-04-hotspot-stops-working/) , 目前最容易的做法就是将这个包降级至 wpa_supplicant-2.9
, 然后就一切正常, 记录如下。
使用 nano 编辑 /etd/apt/source.list
文件:
sudo nano /etc/apt/sources.list
将下面的配置添加到文件的结尾, 并保存:
deb http://old-releases.ubuntu.com/ubuntu/ impish main restricted universe multiverse
deb http://old-releases.ubuntu.com/ubuntu/ impish-updates main restricted universe multiverse
deb http://old-releases.ubuntu.com/ubuntu/ impish-security main restricted universe multiverse
运行下面的命令, 获取软件包更新并执行降级:
sudo apt update
sudo apt --allow-downgrades install wpasupplicant=2:2.9.0-21build1
将 wpasupplicant
标记为保持旧版本, 暂不更新:
sudo apt-mark hold wpasupplicant
标记之后, 执行 sudo apt upgrade
时就不会更新这个包了。
运行 wpa_supplicant -v
, 看一下输出, 不出意外的话, 应该看到如下提示:
wpa_supplicant -v
wpa_supplicant v2.9
Copyright (c) 2003-2019, Jouni Malinen <j@w1.fi> and contributors
降级完成之后, 重启系统, 再创建 Wi-Fi 热点就应该正常工作了。
同样的问题也可能出现在 Manjaro 21.3 、 Fedora 36 等包含了
wpa_supplicant-2.10
的 Linux 发行版中, 理论上都可以用这个方法解决。
目前的 Linux 系统一般都会使用 Network Manager 来管理网卡, Network Manager 提供了 nmcli 命令行工具来管理无线网卡, 通过它可以更加优雅的远程开关热点。
查看无线网络列表
nmcli device wifi
开启无线网卡的 Wi-Fi 信号
sudo nmcli radio wifi on
关闭无线网卡的 Wi-Fi 信号
sudo nmcli radio wifi off
开启热点
sudo nmcli device wifi hotspot
这个命令还有更多的参数可以创建新的 Wi-Fi 热点, 可以参考 nmcli 的官方文档。 如果不想敲复杂的命令的话, 可以在设置界面先设置好热点的参数。
查看热点密码
nmcli dev wifi show-password
这个命令会输出一个二维码, 扫描就应该能获取到密码, 但是我测试失败了。
要了解更多 nmcli 的功能, 请查阅 https://networkmanager.dev/docs/api/latest/nmcli.html 。
]]>Ryujinx 是一个开源的任天堂 Switch 模拟器,由 gdkchan 创建, 用 C# 编写。 这个模拟器的目标是提供出色的准确性和性能, 一个用户友好的界面, 以及一致的构建。
为什么要装这个 Ryuginx 模拟器呢, 原因如下:
Ryujinx 的安装还是挺简单的, 直接从 Mac Release Channel 下载最新版, 解压到 /Applications
目录或者 ~/Applications
目录即可。
主要是初始化设置, 根据官方的设置指南,需要下载 Switch 的密钥 (prod.keys
和 title.keys
) 以及系统固件 (Firmware) , 由于版权的原因, Ryujinx 并没有提供下载链接。
不过好在有完成的搜索, 在这个网站 https://prodkeys.net 上可以找到 Switch 最新的密钥和系统固件, 建议下载中文的固件, 这样打开支持中文的游戏, 默认的界面就会是中文了。
密钥有两个文件, prod.keys
和 title.keys
, 退出 Ryujinx , 将这两个文件放到 ~/.config/Ryujinx/system
目录。
系统固件下载下来一般会是个 Firmware.zip 文件, 这个文件不用解压, 启动 Ryujinx , 在工具菜单 (Tools) 下选择安装固件 (Install Firmware), 然后选择从 XCI 或者 ZIP 安装固件 (Install Firmware from XCI or Zip) , 选择下载好的系统固件文件安装即可。
调整 Ryujinx 默认的选项与设置:
中国
, 系统语言设置为 中文(简体)
, 系统时区设置为 Asia/Shanghai
, 系统时钟设置为当前时间, 注意 启用 VSync
一定要勾选, 否则稳定性会变差, 闪退机率会高很多;Use Hypervisor
不要勾选, 否则稳定性也会降低, 增加闪退机率;Ryujinx 支持多用户, 点击 选项
> 管理用户账户
菜单, 可以管理多个用户账户, 每个用户都有自己的档案, 保存自己的游戏记录等。
RyuPlayer
;完成上面的设置之后, 再下载到你喜欢的 Switch 游戏, 就可以玩了。
至于 Switch 游戏哪里来, 如果你有卡带的话, 可以用特殊的工具导出来, 网上也有不少教程, 可以自己动手试一下。 当然也可以下载别人共享的, 比如 xxxxx 520 就有很多网友共享的 Switch 游戏, 下载自己喜欢的即可。
对于 Ryujinx 模拟器来说, 尽量下载 XCI 格式的游戏文件, 兼容性好一些。
以 Switch 上著名的游戏 塞尔达传说-旷野之息
为例(没错, 就是传说中被 原神
致敬 (chaoxi) 的游戏), 游戏体验大概是这样这样:
游戏刷新率: 塞尔达传说-旷野之息
是 Switch 平台的大型游戏, 游戏刷新率平均是每秒 20 帧左右, 简单场景可以到 30 帧 (Switch 满帧就是30 帧), 在电脑的屏幕上, 还是能感觉到较明显的卡顿, 属于基本能玩的水平。 如果是玩一些小型的游戏, 则全部可以以 30 帧来运行。
模拟器可以通过有金手指来调整刷新率,强制 60 帧来运行, 我就没有尝试了。
资源占用: Ryujinx 在 MacBook 14 M1 Max 上运行 塞尔达传说-旷野之息
这样的大型游戏时, 资源占用情况为:
功耗与散热: 使用 Ryujinx 运行游戏时虽然占用大量的 CPU 和 GPU 资源, 但是整体功耗也不大, 散热压力不大, 只需用 Mac Fans Control
软件将风扇固定在 3000 转, 即可轻松压制, 噪声也几乎可以忽略了;
电池续航: 这个是最拉的, 满电估计只能运行 2 小时左右。
+
和 -
键, macOS 还支持 MFi 认证的手柄, 我刚好有一个 MFi 手柄, 在 iPad 上游戏或者使用 PlayCover 玩 iOS 游戏都能完美的识别, 但是 Ryujinx 却不能正确识别, 已经按照 Ryujinx 官方的提示, 向 SDL 项目提交了问题以及对应的手柄配置, 希望在将来的版本中能够支持。Ryujinx 是为数不多, 或者是仅有的适配苹果原生 M1 芯片的模拟器, 在 Mac M1 上算是基本能用的状态, 而且标记为可以玩的游戏也很多, 如果你想尝试, 也未尝不可。
我觉得 Ryujinx 目前模拟的效率比较低, 貌似没有发挥 M1 芯片的全部能力, 比如不能使用 Hypervisor , 显卡的能力也没有完全发挥出来, Ryujinx 官方也发布了 MacOS upstreaming roadmap , 看来官方还是比较重视 MacOS 的, 一起期待未来的版本吧。
]]>后端技术主要还是 .NET 为主, 估计我也很难全面转向其它的技术方向, 毕竟接触 .NET 20 年了, 太熟悉了, 很难舍弃。
我维护的 .NET 项目均升级到了最新的 .NET 7 , 本来以为是常规升级, 但是由于 .NET 7 默认启用了 nullable
检查, 为了适应这个新特性, 许多代码都需要进行相应的修改, 程序的健壮性会增加不少, 随后会专门针对这个特性再写一篇。
前端一直用的是 Angular , 可以说是很棒的框架, 但是由于目前国内的现状, Angular 可以说是后继无人, 因此不得不寻找替代的前端技术框架, 基本上就只有 Vue 和 React 选择了, 经过一段时间的尝试, 决定开始转向生态更好也更开放的 React , 如果有时间, 会写几篇入门教程或者心得什么的。
学习并掌握了前端的转译/打包工具 rollup 和 esbuild , 不仅可以自己控制如何转译、 分包、 模块转换等常规操作, 甚至还尝试过自己写一些插件, 掌握了这些之后, 可以完全按照自己的想法来掌控前端的项目, 也算是有不小的收获。
前端模块化正在尝试全面转向浏览器原生支持的 ES6 模块化, 放弃传统的 umd 模块, 也是大势所趋了。 不过有两个痛点, Safari 和 移动端浏览器, 目前支持比较差。
WebGIS 之前这一块一直使用 ESRI 的 ArcGIS API for JavaScript (简称 ArcGIS JS API), 现在已经改名为 ArcGIS Maps SDK for JavaScript (简称可能 ArcGIS JS SDK), 反正就是同一个东西了。 不过 ArcGIS JS SDK 虽然功能全面, 也非常专业, 但是也非常的笨重, 很难在移动端使用, 而且很多功能必须和 ArcGIS 的服务端绑定才能使用, 没有对应的服务端的话, 很多功能都不可用, 或者很难使用。
所以现在更加倾向于使用开源的 Mapbox GL JS, 和 ArcGIS 的策略不同, Mapbox 的规范和工具都是开放的, 你可以自己做需要的服务, 也可以付费使用他们提供服务, 为对开发者提供了极大的开放性和灵活性。
由于工作中要用到大量的 GDAL 操作, 单纯依靠 Shell 脚本很难满足需要了, GDAL 提供两种 SDK , C++ 和 Python, 因此学习了 Python 这个胶水语言, 却意外的开辟了另一个技术方向。 掌握了 Python 之后, 不仅 GDAL 使用更加灵活, 而且还可以在 Linux 系统上和 Shell 无缝衔接, 真不愧为胶水语言。
2022 年的技术总结大概就这么多了, 希望接下来的 2023 年能有新的收获。
]]>如果你能看 Twitter 的话, 可以直接看这个主题贴。
2022年11月26日15点25分,收到12320的电话,初筛阳性,感觉轻微发烧,没有测体温, 这是要准备去方仓医院隔离了。
下午6点,抗原复测先阳性,体温39°,自我感觉和感冒症状一样,头疼,浑身肌肉酸痛。 只能按照防疫要求收拾东西准备去方仓医院隔离。
27日凌晨,到达传说中的琶洲方仓医院, 第一印象是这样的。
有兴趣可以分别看一下这两个视频,就不直接贴出来了: 视频1 视频2 。
只能一句话来形容, 很难想象这是所谓的医院!
方仓医院是管饭的,看起来还挺不错的样子!
但是你一吃就知道了, 很难下咽, 吃一口就想吐🤮的味道, 真不知道是怎么做出来的。
当然,也不排除跟味觉暂时失灵有关, 后来几天感觉没那么难吃了, 也可能是习惯了, 不吃不行啊, 不然哪来的抵抗力。
方仓做核酸检测时间间隔要大于24小时,有人会看你的扫码时间。由于第一天是凌晨才到的,所以去的比较晚,好歹赶上了尾班车。没想到第二天再去排队,被告知不超过24小时,不用做了。
第二天惊奇的发现这里居然不做消杀,不做消杀,出仓的人把东西收拾一下,分开袋子装好,然后床空了,就等着下一个人来继续住。
厕所、淋浴间很脏,有很多积水,非常容易滋生各种蚊虫和细菌,广交会的时候不比这时候人少吧,难道厕所也是这样子?
志愿者有两种,穿制服的“正规”志愿者和带袖章的志愿者。穿制服的志愿者不在方仓住宿,每天过来“上班”,他们的抵抗力真的是超强。而穿戴袖章的志愿者则是方仓内的患者,他们的隐形的福利是香烟,因为制服志愿者可以每天出入方仓,可以代买。所以经常可以看到三五个志愿者躲在角落里大快朵颐的吸烟。
再说说患者,大家都是轻症或者无症,也就无所顾忌了,有保持各种瑜伽姿势睡觉的,刷抖音的,跳广场舞,自娱自乐。我跟几个聊得来的组了个跑步团,上午下午分别绕方仓跑几圈,打发时间,锻炼身体。
28日晚上洗头洗澡,我头发很油,所以必须隔天洗一次头,否则就跟戴了一顶帽子感觉差不多。
29日,早早起来,洗漱完毕,吃完早餐第一波就做了核酸。休息一下,然后去洗衣服,因为前一天的衣服没有洗,怎么洗,当然是手洗了。
衣服洗完了,看看地上的水, 厕所就不拍了。
顺便再说两玄幻的事情,我们小区的物业经理和几个保安是阴性,居然也被拉到方仓来了。楼上的邻居初筛阳性,年龄较大,送去了医院,结果在医院测了几天核酸,一直是阴性,人家投诉啊,投诉结果说两小时收拾东西,马上回去。可是医生居然说既然来了,就安心住下吧,给你安排一个单间,多么奇幻的操作!
再说下医护情况,一般情况下根本没人理,没有任何医护问过我的情况,也没有发什么药品。也不能说什么药都没有,只有紧急退烧药和莲花清瘟,对,就是钟南山站台(DaiHuo)的莲花清瘟。
医生说的最多的话就是多喝水,别总是想着吃药。 好吧, 抓过来就是为了吃盒饭和睡硬板床。
29号的核酸到了30号早上还没出结果,没出结果就是阳性。想想确实是,方仓的人默认都是阳性患者。
30号早上有医生用电子测温计测量体温,什么也没问,应该是没什么事,不需要理你。
难道是要变天?发现所有的初阳记录都不见了?先前莲花清瘟一小条都不舍得给,现在一次给一盒,终于也看到有人打扫了。 出去的规矩也改了,只要连续3天阴性就可以,不要求7天了,也不再需要2天来走流程。
今天多了个新的娱乐活动,套圈😁,奖品是泡面和可乐之类的,由于天天吃盒饭,所以这两个还是非常抢手的。
在经历了从阴到阳,再从阳到阴,中间还反复了几天, 心情烦得不得了, 终于出现了连续三天阴的结果, 我可以出仓了, 这场闹剧也终于要结束了。
最后还有个小插曲,出仓时统一有大巴拉到广州南站,还要拉到公交站里面,然后才能各自回家,这是什么操作?
很多人都问我,是什么感受,你可以看看这个视频体验一下,阴性的和阳性的分别是什么感受,虽然不是我住的,但是也差不多!
多年以后,我们回顾这段历史,希望不是个大笑话!
最后,方仓医院的阳性患者可以直接申请回家了,政策也放开了,我住了个寂寞!
]]>首先, cifs-utils
一定要安装, 系统自带的 mount
命令虽然也能用, 但是选项不多, 或者说不够多。 在 Ubuntu 系统上, 安装命令为:
sudo apt install cifs-utils
先创建一个凭证文件, 保存访问共享存储使用的用户名和密码, 这样会安全一些, 如果要多次挂载共享存储, 也可以共用这个凭证文件。
先创建 /etc/credentials
目录,
sudo mkdir /etc/credentials
编辑 /etc/credentials/test
文件, 保存共享存储的用户信息。
sudo nano /etc/credentials/test
这个文件的内容如下:
username=server_username
password=user_password
domain=domain
设置这个文件的所有者和权限,
sudo chown root:root /etc/credentials/test
sudo chmod 600 /etc/credentials/test
现在尝试使用这个凭证文件来测试挂载共享存储,将 //192.168.0.2/test
挂载到 /mnt/test
目录, 命令为:
sudo mount -t cifs -o credentials=/etc/credentials/test //192.168.0.2/test /mnt/test
如果没有错误提示, 则表示挂载成功。
如果没有安装
cifs-utils
, 就会出现错误提示, 因为不支持credentials
参数。
但是这样挂载的目录 /mnt/test
下的目录、文件的所有者属于 root 用户, 只有 root 能正常使用, 其它用户必须通过 sudo 命令才能使用。 而当服务无法使用 root 用户时, 就无法直接使用共享存储了。
通过查阅 mount.cifs 的帮助信息, 发现在挂载共享目录时还可以指定用户ID、用户组ID、文件及目录模式。
查询当前用户的ID信息可以直接使用 id
命令, 比如:
id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),120(lpadmin),131(lxd),132(sambashare),998(docker)
其它用户的可以命令 grep username /etc/passwd
来查找
grep ubuntu /etc/passwd
ubuntu:x:1000:1000:ubuntu,,,:/home/ubuntu:/bin/bash
找到用户的 uid
和 gid
之后, 就可以在挂载命令时指定挂载目录下的文件所有者, 也可以同时指定目录及文件的权限, 命令如下:
sudo mount -t cifs -o credentials=/etc/credentials/test,uid=1001,gid=1002,dir_mode=0755,file_mode=0755,iocharset=utf8 //192.168.0.2/test /mnt/test
这样挂载的目录 /mnt/test
, uid 为 1000 的用户可以直接使用, 不需要再借助 sudo
。
如果要在系统启动时自动挂载共享存储, 需要将挂载信息保存在 /etc/fstab
文件中, 在文件中添加下面的内容, 和上面的命令差不多:
# <file system> <mount point> <type> <options> <dump> <pass>
//192.168.0.2/test /mnt/test cifs credentials=/etc/credentials/test,uid=1001,gid=1002,dir_mode=0755,file_mode=0755,iocharset=utf8 0 0
保存 /etc/fstab
文件之后, 用 mount
命令测试一下
mount /mnt/test
如果没有任何错误提示, 就可以正常使用了。
]]>虽然借助苹果的 Rosetta 转译程序, 可以直接运行原来的 x64 应用, 但是效率不高, 因此安装对应的 Arm 版本的软件才是最佳的选择。
从 AppStore 上下载的应用, 基本上已经都是通用 (Universal) 应用了, 只需要在新电脑上重新下载即可。 而不是在 AppStore 上下载的软件, 则只能自己去官方网站上寻找对应的 Arm 版本。
Rosetta 在最近几年内还是需要的, 毕竟有很多旧的软件还不支持 Arm 。
这个软件令人比较头疼, 但是又不得不安装, 而且没有官方的迁移方法, 只能手工按照下面的方法迁移:
~/Library/Containers/WeChat
直接用 AirDrop 发送到新电脑;~/Library/Containers/WeChat
;也可以用同样的办法迁移 QQ 的聊天文件, 或者说从 AppStore 下载的软件都可以用这种方法进行迁移,当然这个没有试过, 仅仅测试了微信和QQ。
项目中有很多临时文件, 特别是 node_modules
目录, 占用大量的空间, 不仅复制过去非常的耗时, 而且就算复制过去了也不能直接使用, 因此把它们清理掉再迁移。
查找制定目录下全部的 node_modules 目录并打印大小
在 Linux 和 Mac 上, 输入命令
find . -name "node_modules" -type d -prune -print | xargs du -chs
在 Windows 上,可以这样
FOR /d /r . %d in (node_modules) DO @IF EXIST "%d" echo %d"
在 Linux 和 Mac 上, 输入命令
find . -name "node_modules" -type d -prune -print -exec rm -rf '{}' \;
在 Windows 上, 可以这样
FOR /d /r . %d in (node_modules) DO @IF EXIST "%d" rm -rf "%d"
如果有 Powershell , 还可以这样
Get-ChildItem -Path "." -Include "node_modules" -Recurse -Directory | Remove-Item -Recurse -Force
可以使用同样的办法来删除编译生成的临时文件,比如 bin
、 obj
、 class
、 dist
、logs
等临时目录;
清理完这些临时目录文件, 项目目录由 30G 缩减为 10G , 然后再用 AirDrop 发送到新电脑即可。
上面清理目录的命令来自 How to Delete ALL node_modules folders on your machine , 其中 Windows 和 Powershell 版本的没有经过测试。
这个本来按照官网的命令可以直接安装就可以了, 但是由于目前的网络环境不佳直接导致无法克隆 homebrew/core
下来, 最后找到的办法是将环境变量 HOMEBREW_CORE_GIT_REMOTE
设置为 git@github.com:Homebrew/homebrew-core.git
, 即使用 ssh 地址才可以顺利克隆下来。
安装 Homebrew 的第三方 Cask 时, 也可以指定 git 代码库的地址, 比如:
brew tap beeftornado/rmtree git@github.com:beeftornado/homebrew-rmtree.git
接下来就是继续安装 Homebrew 下的包, 和原来的用法一致。
DockerDesktop for Mac 已经适配 M1, 不过原来的 x64 镜像(特别是数据库)几乎都不能用, 不过大部分 Linux 镜像都有 Arm 版本, 碰到没有的也可以自己编译一个出来, 使用上影响不大。
但是编译出来的镜像也是 Arm 架构的, 不能直接部署到 x64 服务器上使用, 虽然可以强制性指定 --platform linux/x64
编译出 x64 版本的镜像, 但是又不能直接测试和使用, 看来还是不能完全脱离 Intel 机器。
试过 UTM 、 VMWare Fusion 、 Parallel Desktop , 只能安装 Arm 版本的系统, 对 Linux 支持的比较好, Windows 的支持其实都一般, 毕竟官方是不支持虚拟 Windows 系统的。
如果确实想运行一些 Windows 软件的话, 还有一个选择,那就是 Wine 和 CrossOver , Wine 只能运行 64 位的 Windows 软件, 而 CrossOver 则实现了 Wine32on64 , 可以在运行 32 位的 Windows 软件, 只是版本稍微低一些。
Wine 和 CrossOver 可以使用第三方的 Homebrew 公式(Formula) Gcenx/homebrew-wine 来安装,也可以在 releases 页面直接下载, 如果想尝试最新的 wine-devel 以及 wine-staging , 则可以在 Gcenx/macOS_Wine_builds 下载。
最新的 wine-devel 内置 VKD3D (用 VulkanAPI 实现 Windows 的 DirectX) 对 Windows 游戏支持的比较好, 可以抽时间试一下前段时间白嫖的古墓丽影四部曲。
虽然有 HDMI 接口, 可以直接连接 HDMI 接口显示器、投影仪、电视机之类的, 但是如果需要连接 USB 键鼠 (虽然是键线分离的,但一般不能通过 USB Type-C 直接连接) 和 U 盘之类的话,OTG 线或扩展坞还是需要一个的。
不想买扩展坞的话,也许买一个带全功能 USB Type-C 的显示器是更好的选择, 比如 Dell 的 U2421E 。
]]>