录屏软件推荐(OBS)

Open Broadcaster Software | OBS

生成的视频很小,支持视频剪辑和音频编辑。

  • 1、可以添加视频来源,录屏就是显示器,若有多个显示器也可以切换
  • 2、开始录制,结束录制后会自动生成到电脑视频目录,默认 MKV 格式,能录制电脑声音和麦克风声音
  • 3、可以将 MKV 格式转为 MP4 等其他格式
  • 4、可以设置快捷键,比如 F7 开始录制,F8 停止录制
  • 5、可以录制过程中切换场景和来源,视频会自动修改

ReactNative入门

参考搭建开发环境,其中有些步骤可能需要改动。

Windows 下搭建 ReactNative 开发环境:

安装 Node

不需要安装 Python 2

安装 react-native-cli 命令行工具

1
npm install -g yarn react-native-cli

安装 Android Studio

需要安装 Android Studio 3.4 以上的版本

配置 Android 环境变量

添加 ANDROID_HOME,值为 C:\Users\fuma\AppData\Local\Android\Sdk,修改 PATH,添加两个路径:

  • C:\Users\fuma\AppData\Local\Android\Sdk\platform-tools
  • C:\Users\fuma\AppData\Local\Android\Sdk\tools

初始化项目

1
react-native init rnDemo

启动模拟器

点击右上角的模拟器图标,

打开的列表中应该会有一个默认的模拟器,双击启动即可。

启动项目

进入项目中,允许命令

1
npm run android

注意会启动这样一个窗口,绿色进度条到 100%,就代表加载成功。

然后就可以看到 React Native 应用在模拟器中的效果。

油猴脚本开发

Tampermonkey

油猴脚本模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ==UserScript==
// @name New Userscript
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match http://*/*
// @grant none
// ==/UserScript==

(function() {
'use strict';

// Your code here...
})();

GM_xxx 方法在使用前,需要先在顶部标明,例如 // @grant GM_openInTab ,这样就可以使用 GM_openInTab 方法来打开新页面

支持的标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@name
@namespace
@version
@author
@description
@homepage, @homepageURL, @website and @source
@icon, @iconURL and @defaulticon
@icon64 and @icon64URL
@updateURL
@downloadURL
@supportURL
@include
@match
@exclude
@require
@resource
@connect
@run-at
@grant
@noframes
@unwrap
@nocompat

其他接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
unsafeWindow
Subresource Integrity
GM_addStyle(css)
GM_deleteValue(name)
GM_listValues()
GM_addValueChangeListener(name, function(name, old_value, new_value, remote) {})
GM_removeValueChangeListener(listener_id)
GM_setValue(name, value)
GM_getValue(name, defaultValue)
GM_log(message)
GM_getResourceText(name)
GM_getResourceURL(name)
GM_registerMenuCommand(name, fn, accessKey)
GM_unregisterMenuCommand(menuCmdId)
GM_openInTab(url, options), GM_openInTab(url, loadInBackground)
GM_xmlhttpRequest(details)
GM_download(details), GM_download(url, name)
GM_getTab(callback)
GM_saveTab(tab)
GM_getTabs(callback)
GM_notification(details, ondone), GM_notification(text, title, image, onclick)
GM_setClipboard(data, info)
GM_info
<><![CDATA[your_text_here]]></>

其他注意点

  • 油猴 GM_download 方法无法下载 blob 资源转化的 URL

VSCode插件

起点

现在最常用的开发工具就是 VSCode 了,插件可以丰富 VSCode 的功能,提升写代码写文章的效率。

你或许不需要自己开发一个插件,因为插件市场有足够多的插件,查看插件市场

不过,别人写的插件,可能并不能刚好满足你的要求,要么多了、要么少了。自己写插件,可以尝试融合多个插件为一体,可以将大插件中的某个小功能抽取出来。

首先,学习 VSCode 插件开发可以先看官方文档,然后看看 Github 上的开源项目。
在 VSCode 的插件介绍页面中,基本是每个 VSCode 都有一个“存储库”,点击即可前往 Github 仓库。

开发

VSCode 插件可以用 TypeScript 编写,使用官方脚手架 https://github.com/Microsoft/vscode-generator-code 可以生成模板。

VSCode 能识别插件项目,按 F5 就会启动一个新的 VSCode 窗口用于调试。

代码示例

1
2
// 默认方式打开本地文件
vscode.env.openExternal(vscode.Uri.file('F:\\极乐净土.mp4'));

打开 Webview 开发者工具

ctrl+shift+p,输入 Webview 开发,即可看到。

打包、安装

每次更新,都应该提升 version,同一个 version,VSCode 好像会保留缓存。

VSCode 插件可以通过 VSIX 安装,代码可以打包成 VSIX。然后打开插件标签,通过 VSIX 安装插件。

插件推荐

  • GitLens
  • Markdown All in One
  • favorites
  • vscode-icons
  • REST Client
  • Import Cost
  • Prettier
  • Color Info

参考资料

Electron入门

主进程和渲染器进程

https://electronjs.org/docs/tutorial/application-architecture

只有一个主进程,每个页面都是单独的渲染进程。

主进程和渲染进程的通信方式如下:

  • ipcMainipcRenderer 是传统方式
  • remote 提供了一种简单方法,可以在渲染进程调用主线程的全局变量

如何在 React 中引入 ipcRenderer?

需要在 webpack 中设置 target 为 electron-renderer,参考https://stackoverflow.com/questions/39332546/using-electrons-ipcrender-from-inside-a-react-component。

1
target: 'electron-renderer',

ipcMain 与 ipcRenderer

1
2
3
4
5
6
7
8
9
10
11
// react 页面中
import { ipcRenderer } from 'electron';

ipcRenderer.send('send-somemessagee');

// 主线程接收
import { app, BrowserWindow, ipcMain } from 'electron';

ipcMain.on('send-somemessagee', (event, arg) => {
console.log('===send somemessagee====', arg)
})

在 React 组件中使用 remote

1
2
3
4
5
import electron, { ipcRenderer } from 'electron';
const { BrowserWindow } = electron.remote;

let win = new BrowserWindow({ width: 800, height: 600 })
win.loadURL('https://github.com')

VSCode 中调试主线程

针对 https://github.com/electron-react-boilerplate/electron-react-boilerplate。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "调试主线程",
"cwd": "${workspaceRoot}",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
},
"env": {"NODE_ENV":"development"},
"args" : ["-r", "@babel/register", "./app/main.dev.js"]
}
]
}

打包源码为 asar

https://electronjs.org/docs/tutorial/application-packaging

Hexo源码分析

Hexo Github,Hexo 其实包含了许多的 npm 包。其中 hexo 是主体,hexo-cli 是调用工具,hexo-* 是一些辅助包或插件。

每个使用 Hexo 创建的静态网站项目,都包含 package.json。hexo-cli 工具调用的就是项目目录下的 hexo。

扩展对象

在Hexo中,包含如下扩展类型,每个扩展对象是一个容器,一个事件注册机。基本上 hexo 的操作都是围绕这些对象来实现的。

1
2
3
4
5
6
7
8
9
10
11
this.extend = {
console: new extend.Console(),
deployer: new extend.Deployer(),
filter: new extend.Filter(),
generator: new extend.Generator(),
helper: new extend.Helper(),
migrator: new extend.Migrator(),
processor: new extend.Processor(),
renderer: new extend.Renderer(),
tag: new extend.Tag()
};

继承 EventEmitter

Hexo采用构造-原型组合模式定义类,采用组合继承的方式继承Node中EventEmitter模块,更容易得通过on与emit发布与订阅事件。

这里会用到 Reflect 对象和 util.inherits 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
'use strict'
const EventEmitter = require('events');
const util = require('util');

function Demo() {
Reflect.apply(EventEmitter, this, []);
}

//继承
util.inherits(Demo, EventEmitter);

const demo = new Demo();

const one = () => {
console.log('hi demo');
}

demo.on('myEvent', one);

demo.emit('myEvent');
demo.emit('myEvent');
demo.removeListener('myEvent', one);
demo.emit('myEvent');

hexo-cli 命令定义在哪里

在 hexo/lib/plugins/console 文件夹内,注意 hexo server 是一个特殊的命令,需要安装 hexo-server 才能使用。

hexo-server

hexo-server 是一个独立的模块,需要独立安装在博客项目中。其生效的方式特别巧妙,是通过读取 package.json 中的包名来实现的。

安装 npm 包即可附加功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// hexo/lib/hexo/load_plugins.js
function loadModuleList(ctx) {
if (ctx.config && Array.isArray(ctx.config.plugins)) {
return Promise.resolve(ctx.config.plugins).filter(item => typeof item === 'string');
}

const packagePath = join(ctx.base_dir, 'package.json');

// Make sure package.json exists
return fs.exists(packagePath).then(exist => {
if (!exist) return [];

// Read package.json and find dependencies
return fs.readFile(packagePath).then(content => {
const json = JSON.parse(content);
const deps = Object.keys(json.dependencies || {});
const devDeps = Object.keys(json.devDependencies || {});

return deps.concat(devDeps);
});
}).filter(name => {
// Ignore plugins whose name is not started with "hexo-"
if (!/^hexo-|^@[^/]+\/hexo-/.test(name)) return false;

// Ignore typescript definition file that is started with "@types/"
if (/^@types\//.test(name)) return false;

// Make sure the plugin exists
const path = ctx.resolvePlugin(name);
return fs.exists(path);
});
}

hexo-server 里面有一个中间件的概念,但不是 Express 的中间件。

hexo-server 扩展了 hexo 的命令:

1
2
3
4
5
6
7
8
9
10
hexo.extend.console.register('server', 'Start the server.', {
desc: 'Start the server and watch for file changes.',
options: [
{name: '-i, --ip', desc: 'Override the default server IP. Bind to all IP address by default.'},
{name: '-p, --port', desc: 'Override the default port.'},
{name: '-s, --static', desc: 'Only serve static files.'},
{name: '-l, --log [format]', desc: 'Enable logger. Override log format.'},
{name: '-o, --open', desc: 'Immediately open the server url in your default web browser.'}
]
}, require('./lib/server'));

json 文件作为 Database

使用库 https://www.npmjs.com/package/warehouse,这是 Hexo 作者自己提供的。
有个类似的库 https://www.npmjs.com/package/lowdb

lowdb 可以在多种环境中使用,提供了不同的 adapter。

pre-commit

1
2
3
4
5
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}

参考文章:

如何使用BrowserHistory

我们知道,使用 React-Router 有 HashHistory 和 BrowserHistory。HashHistory 比较简单,但是 URL 中带 #,不美观,而且会遇到 / 和 # 不知道怎么放的问题。BrowserHistory 配置起来麻烦一点,但为了去掉 # 还是值得的。

Node 端路由修改

1
2
//注意这里是正则表达式,没有单引号
router.get(/\/app(\/.*)?/, auth, controller.home.index);

Client 端修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import { AppContainer } from 'react-hot-loader';
import App from './pages/App';
import history from '@/app/history';

const initialState = {};
const store = configureStore(initialState, history);
const MOUNT_NODE = document.getElementById('root');

function render(Component) {
ReactDOM.render(
<AppContainer>
<Provider store={store}>
<Router history={history}>
<Component />
</Router>
</Provider>
</AppContainer>,
MOUNT_NODE
);
}

render(App);


// history.js
// 将 history 独立出来,是为了后面在组件中使用
import { createBrowserHistory } from 'history';
const history = createBrowserHistory()

export default history;


// App.tsx
import * as React from 'react';
import { connect } from 'react-redux';
import { Route } from 'react-router-dom';
import { withRouter } from 'react-router';
import { LocaleProvider } from 'antd';
import ZHCN from 'antd/lib/locale-provider/zh_CN';

// 省略 OwnsProps 和 StateProps 的定义

class App extends React.Component<OwnsProps, StateProps> {
constructor(props) {
super(props);
}

render() {
return (
<LocaleProvider locale={ZHCN}>
<div className='content'>
<Route path={`/app/overview`} component={Overview} />
<Route path={`/app/school`} component={School} />
<Route path={`/app/teacher`} component={Teacher} />
<Route component = {NotFound}/>
{this.props.children}
</div>
</LocaleProvider>
);
}
}

function mapStateToProps(state) {
}

const withConnect = connect(mapStateToProps, null);

export default withRouter(withConnect(App) as any);

怎么使用 history.push

使用 BrowserHistory 之后,window.location.href = ‘target’ 这样会导致页面刷新,使用 react-router-dom 中的 <Link> 可以正常工作,但是 组件却无法从 <Router history={history}> 中继承 history。解决办法就是就 history 放到一个独立文件中,然后在组件中引入这个 history。

参考链接:https://stackoverflow.com/a/42679052

1
2
3
import history from '@/app/history';

history.push('/app/teacher');

Node解析XML

使用xml-js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
'use strict'
const path = require('path');
const fs = require('fs-extra');
const convert = require('xml-js');

const enbx = 'C:\\Users\\cvter\\Desktop\\模板';

const documentXml = path.join(enbx, 'Document.xml');

const ds = fs.readFileSync(documentXml);
//xml 转为 js 对象,{compact: true, spaces: 4} 是必要的
var result1 = convert.xml2js(ds, {compact: true, spaces: 4});

result1.Document.Name._text = 'test change';
//js 对象 转为 xml,{compact: true, spaces: 4} 是必要的
const xml = convert.js2xml(result1, {compact: true, ignoreComment: true, spaces: 4});

console.log(xml);

nestjs学习

nestjs 被称为 Node 版 Spring,使用 TypeScript 开发。从源码 @next/core 可以看出来,是基于 Express 开发的。

装饰器与注入对象的对应关系

装饰器 对象
@Request()/@Req() req
@Response()/@Res() res
@Next() next
@Session() req.session
@Param(param?: string) req.params / req.params[param]
@Body(param?: string) req.body / req.body[param]
@Query(param?: string) req.query / req.query[param]
@Headers(param?: string) req.headers / req.headers[param]

路由

路由支持通配符,支持 @GET,@Put(), @Delete(), @Patch(), @Options(), @Head(), and @All() 等装饰器。

可注入对象

使用 @Injectable() 装饰,还可以通过 @Inject 注入属性。

1
2
3
4
5
6
7
8
9
10
@Controller('cats')
export class CatsController {
//直接注入 CatService 对象
constructor(private readonly catsService: CatsService) {}

@Post()
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
}

模块

使用 @Module 装饰,分为四个部分:

  • imports 引入的其他模块
  • exports 对外公开的资源
  • controllers 路由
  • providers 资源,比如 Service

模块可以共享,通过 imports 和 exports,还可以通过 @Global 定义全局模块,全局模块建议只定义一次。

中间件

中间件可以由函数和类实现,如果是类,需要实现 NestMiddleware 接口。

中间件示例

1
2
3
4
5
6
7
8
9
10
11
import { Injectable, NestMiddleware, MiddlewareFunction } from '@nestjs/common';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
resolve(...args: any[]): MiddlewareFunction {
return (req, res, next) => {
console.log('Request...');
next();
};
}
}

管道Pipe

需要实现 PipeTransform 接口,提供 transform() 方法。可以通过 @UsePipe 应用管道,也可以给特定参数应用管道。

1
2
3
4
5
6
7
8
9
10
@Post()
@UsePipes(new JoiValidationPipe(createCatSchema))
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}

@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id) {
return await this.catsService.findOne(id);
}

守卫Guard

需要实现 CanActivate 接口,可以实现角色管理和鉴权。

拦截器Interceptors

循环依赖

使用 @Inject,forwardRef 来解决循环依赖。

1
2
3
4
5
6
7
8
@Injectable()
export class CatsService {
constructor(
//使用forwardRef
@Inject(forwardRef(() => CommonService))
private readonly commonService: CommonService,
) {}
}

异常处理

可以通过 @Catch 捕获异常

GraphQL

nestjs 提供了对 GraphQL 的支持。

微服务

nestjs 提供了一些微服务的实现方式,通信方式包括 Redis、 RabbitMQ、gRPC 等。

nestjs 支持直接创建微服务,使用 NestFactory.createMicroservice。

1
2
3
4
5
6
7
8
9
10
11
import { NestFactory } from '@nestjs/core';
import { Transport } from '@nestjs/microservices';
import { ApplicationModule } from './app.module';

async function bootstrap() {
const app = await NestFactory.createMicroservice(ApplicationModule, {
transport: Transport.TCP,
});
app.listen(() => console.log('Microservice is listening'));
}
bootstrap();

nest 的一些问题

  • 目录未约定
  • 没有内置配置管理功能

用面向对象的方式封装redux

天下苦 Redux 久矣。

我们在使用 Redux 的过程中,总是需要定义如下代码:

  • actionType
  • action
  • reducer

然后在代码中按如下方式使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import * as DemoActions from '@actions/demoaction';
import * as TestActions from '@actions/testaction';

foo = () => {
const { actions } = this.props;

actions.demo();
actions.test();
};

function mapDispatchToProps(dispatch) {
return {
dispatch,
actions: {
...bindActionCreators(DemoActions, dispatch),
...bindActionCreators(TestActions, dispatch)
}
};
}

const withConnect = connect(mapStateToProps, mapDispatchToProps);

export default withConnect(DemoComponent);

上面的展示的 Redux 传统使用方式到底有哪些缺点呢?

  • mapDispatchToProps 很繁琐,引入各 action 文件后再 bindActionCreators 很枯燥
  • const { actions } = this.props; 很繁琐,props 里面的 actions 一般是不会改变的,为什么每次使用都需要获取呢?
  • 多个 action 的方法全部合并到一个 actions 里面,使用者根本不知道某个函数来自哪个 action
  • 使用 action 中的函数 VSCode 没有智能提示

我也常常在思考如何在项目中优化 Redux 的使用方式。

业内一直都有对 Redux 的各种封装,例如下面这些库:

刚好我们最近开始在项目中全面推广 TypeScript,所以考虑使用面向对象的方式来改造 Redux。

action 的本质

action 本质上无非是提供一些函数以供 dispatch

1
2
3
4
5
6
7
//demoAction.js
export const foo = params => dispatch => {
dispatch({
type: types.DEMO,
data: params
});
};

我们完全可以使用 TypeScript 将 action 改造为 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//demoAction.ts
class DemoAction {
private dispatch: any;

// 传入组件的 dispatch
constructor(element: any) {
this.dispatch = element.props.dispatch;
}

demo = params => {
this.dispatch({
type: types.DEMO,
data: params
});
};
}

export default DemoAction;

然后向下面这种方式使用 action,不再需要 mapDispatchToProps 了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import DemoAction from '@actions/demoAction';

export class DemoComponent extends React.Component {
private demoAction:DemoAction;//定义demoAction,指定类型
constructor(props) {
this.demoAction = new DemoAction(this);
}

foo = () => {
const { actions } = this.props;

// 此处对智能提示支持很好
this.demoAction.demo();
};
}

// function mapDispatchToProps(dispatch) {
// return {
// dispatch,
// actions: {
// ...bindActionCreators(DemoActions, dispatch),
// ...bindActionCreators(TestActions, dispatch)
// }
// };
// }

// 不再需要 mapDispatchToProps
const withConnect = connect(mapStateToProps);

export default withConnect(DemoComponent);

以上改造的关键在于 dispatch,那么 dispatch 来自哪里?

dispatch 来自哪里

使用 react-redux 的 connect 函数,就可以注入 dispatch 到组件中,参考官方文档:https://react-redux.js.org/api/connect#example-usage

1
export default connect()(TodoApp);

dispatch 还可以从 store 中获取,参考 https://stackoverflow.com/questions/38532433/react-redux-global-function-that-can-dispatch-actions。

1
2
import {store} from "../appRoot/configureStore";
store.dispatch({ ... });

更进一步

我们还可以将 reducer 和 action 放到一起,因为这二者是强关联的。将 reducer 作为类的静态函数,向 reducers 暴露。