问题

不论是面试也好,培训新人也好,自己总结也好,做个这些子集的超集。这里尽量给一手资料的链接,和一些公共维护的文档,也建议大家少看博客这类二手资料/非公共资料。

RoadMap

前端基础

HTML 与 语义化 HTML

css 与 css3

js

js mdn

基本开发技能

git

如何使用 google 搜索/《提问的智慧》

linux/wsl/常用 linux 命令

基本数据结构和算法(https://oi-wiki.org/ , codeforces.com 1900 分以下的题)

设计模式

前端包工具

npm/yarn , nvm

Webpack/Rollup/Parcel

Angular CLI/Nuxt/Next/Gatsby/CRA

Bazel

gulp/npm scripts

样式

sass/less/postcss

tailwind

Angular Material/Element UI/Ant Design

代码风格

Angular Styleguide/Vue Styleguide/Reactjs advanced guides

Angular Schematics

eslint/prettier/git hooks

三方基础

TypeScript

RxJs

状态管理

Flux/@ngrx/Angular Services/Vuex/Redux

PWA

@angular/pwa/Nuxt pwa/Workbox

Router

Vue router / Angular router / React router

非浏览器

Ionic/React Native/Electron

SSR

Flutter

WASM

Testing

单元:Jasmine/Karma/Jest

e2e:

Util

日期 dayjs/Momentsjs

轮播 swiper

图表 echarts

条形码 jsbarcode

二维码 qrcode

2021 frontend roadmap

常用知识

异步通信

callback

Promise

async/await

Observer

RxJs

问题

因为工期,项目上有多个长分支,而最近在看pr的时候,发现有些已经合并过的,在页面上还是展示了变更,但是命令行里 直接diff两个分支不存在

复现

https://github.com/CroMarmot/gitlab-pr-view-bug/blob/master/reproduce.sh

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
# Init Repo
cd /tmp
mkdir gitlab-pr-view-diff-bug && cd gitlab-pr-view-diff-bug
git init
echo "Init" >> init
git add init
git commit -m "init"

# branchs
git checkout -b pre-release
git checkout -b common
git checkout -b feat

# some feature on feat0 branch
echo "feat0" >> feat0
git add feat0
git commit -m "file feat0"

git checkout pre-release
git merge --no-ff --no-edit feat
sleep 1

# some common update
git checkout common
echo "cmm" >> cmm
git add cmm
git commit -m "file cmm"
sleep 1

git checkout pre-release
git merge --no-ff --no-edit common
sleep 1

# feat use common and update some feature
git checkout feat
git merge common --no-edit
sleep 1
echo "feat1" >> feat1
git add feat1
git commit -m "file feat1"
sleep 1

# diff
git log --all --graph --oneline
echo "git diff pre-release feat"
git diff pre-release feat

echo "git diff pre-release..feat"
git diff pre-release..feat

echo "git diff pre-release...feat"
git diff pre-release...feat

echo 'git diff $(git merge-base pre-release feat) feat'
git diff $(git merge-base pre-release feat) feat

# clear
rm -rf /tmp/gitlab-pr-view-diff-bug/

输出

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
Initialized empty Git repository in /tmp/gitlab-pr-view-diff-bug/.git/
[master (root-commit) 7dbccd5] init
1 file changed, 1 insertion(+)
create mode 100644 init
Switched to a new branch 'pre-release'
Switched to a new branch 'common'
Switched to a new branch 'feat'
[feat 5e8b20f] file feat0
1 file changed, 1 insertion(+)
create mode 100644 feat0
Switched to branch 'pre-release'
Merge made by the 'recursive' strategy.
feat0 | 1 +
1 file changed, 1 insertion(+)
create mode 100644 feat0
Switched to branch 'common'
[common 09a3969] file cmm
1 file changed, 1 insertion(+)
create mode 100644 cmm
Switched to branch 'pre-release'
Merge made by the 'recursive' strategy.
cmm | 1 +
1 file changed, 1 insertion(+)
create mode 100644 cmm
Switched to branch 'feat'
Merge made by the 'recursive' strategy.
cmm | 1 +
1 file changed, 1 insertion(+)
create mode 100644 cmm
[feat 9d8b809] file feat1
1 file changed, 1 insertion(+)
create mode 100644 feat1
* 9d8b809 (HEAD -> feat) file feat1
* b9bfda6 Merge branch 'common' into feat
|\
| | * 376c6cc (pre-release) Merge branch 'common' into pre-release
| | |\
| | |/
| |/|
| * | 09a3969 (common) file cmm
| | * 5a08d58 Merge branch 'feat' into pre-release
| |/|
| |/
|/|
* | 5e8b20f file feat0
|/
* 7dbccd5 (master) init
git diff pre-release feat
diff --git a/feat1 b/feat1
new file mode 100644
index 0000000..d7c678b
--- /dev/null
+++ b/feat1
@@ -0,0 +1 @@
+feat1
git diff pre-release..feat
diff --git a/feat1 b/feat1
new file mode 100644
index 0000000..d7c678b
--- /dev/null
+++ b/feat1
@@ -0,0 +1 @@
+feat1
git diff pre-release...feat
diff --git a/feat0 b/feat0
new file mode 100644
index 0000000..05884ff
--- /dev/null
+++ b/feat0
@@ -0,0 +1 @@
+feat0
diff --git a/feat1 b/feat1
new file mode 100644
index 0000000..d7c678b
--- /dev/null
+++ b/feat1
@@ -0,0 +1 @@
+feat1
git diff $(git merge-base pre-release feat) feat
diff --git a/feat0 b/feat0
new file mode 100644
index 0000000..05884ff
--- /dev/null
+++ b/feat0
@@ -0,0 +1 @@
+feat0
diff --git a/feat1 b/feat1
new file mode 100644
index 0000000..d7c678b
--- /dev/null
+++ b/feat1
@@ -0,0 +1 @@
+feat1

注意到 上面 4种比较方式,前两种一致,后两种一致,而对于看一个pr导致的变化,更期望的是前两种(仅多了 feat1)而不是后两种(多了 feat0 和 feat1)

**但实际上 直接的 diff 也不是真正期望看到的,期望的还是如果merge以后和当前的差异 **

解释

搜到了一个gitlab-ce官方讨论的地方,大概意思是用的git diff A...B相当于git diff $(git merge-base A B) B

也就是 当预发布分支 pre-release 合并了公共的一个分支 common 后, 功能分支feat 页合并过公共分支后

那么git merge-base A B 去找的并不是A

而 你所看到的 pr 并不是真正的变化,就离谱

在线

看起来 github 还有这个问题

https://github.com/CroMarmot/gitlab-pr-view-bug/pull/1/files

但 gitlab官方似乎已经改了

https://gitlab.com/YeXiaoRain/gitlab-pr-view-bug/-/merge_requests/1/diffs

参考

https://gitlab.com/gitlab-org/gitlab-foss/-/issues/15140

对于transition的一个核心问题是时间哪里来的

因为name这些感觉能拿到, 拼接也就当然

而 transition包裹的内部元素销毁,如果能拿到时间,那其实跟keep-alive 原理类似,在时间以后再销毁即可

然而我们并没有传递一个时间给元素,它却能知道时间

source code

vue2

src/platforms/web/runtime/transition-util.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export function getTransitionInfo (el: Element, expectedType?: ?string): {
type: ?string;
propCount: number;
timeout: number;
hasTransform: boolean;
} {
const styles: any = window.getComputedStyle(el)
// JSDOM may return undefined for transition properties
const transitionDelays: Array<string> = (styles[transitionProp + 'Delay'] || '').split(', ')
const transitionDurations: Array<string> = (styles[transitionProp + 'Duration'] || '').split(', ')
const transitionTimeout: number = getTimeout(transitionDelays, transitionDurations)
const animationDelays: Array<string> = (styles[animationProp + 'Delay'] || '').split(', ')
const animationDurations: Array<string> = (styles[animationProp + 'Duration'] || '').split(', ')
const animationTimeout: number = getTimeout(animationDelays, animationDurations)

vue3

packages/runtime-dom/src/components/Transition.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
export function getTransitionInfo(
el: Element,
expectedType?: TransitionProps['type']
): CSSTransitionInfo {
const styles: any = window.getComputedStyle(el)
// JSDOM may return undefined for transition properties
const getStyleProperties = (key: string) => (styles[key] || '').split(', ')
const transitionDelays = getStyleProperties(TRANSITION + 'Delay')
const transitionDurations = getStyleProperties(TRANSITION + 'Duration')
const transitionTimeout = getTimeout(transitionDelays, transitionDurations)
const animationDelays = getStyleProperties(ANIMATION + 'Delay')
const animationDurations = getStyleProperties(ANIMATION + 'Duration')
const animationTimeout = getTimeout(animationDelays, animationDurations)

所以

核心是window.getComputedStyle

Docker Harbor

简单说,可以搭本地/局域网 docker仓库

前置条件

Ubuntu 或 其它linux 或 Ubuntu on WSL2

依赖

安装docker:

1
sudo apt install docker.io docker-compose

下载包

经过历史经验教训 用WSL2的不要把包放在win的目录下,要放在wsl2 私有的目录下

https://github.com/goharbor/harbor/releases

解压

1
tar xvzf harbor-offline-installer-*.tgz && cd harbor

SSL等配置

这玩意一定要SSL, 这里用自签

ip查看

1
ifconfig | grep inet
閱讀全文 »

apt

怀念 Debian apt 的好, 在windows上腾讯软件管家成了我的“apt”

以下是一些问题记录

vscode

set git bash as default terminal

windows terminal

windows terminal 双击点击鼠标选择 在单词和行选择切换,而非空格分割和行之间切换

https://stackoverflow.com/questions/60441221/double-click-to-select-text-in-windows-terminal-selects-only-one-word

Git

更应该 不要 win和wsl共用磁盘

https://stackoverflow.com/questions/1580596/how-do-i-make-git-ignore-file-mode-chmod-changes

https://stackoverflow.com/questions/50096060/how-to-switch-branch-when-filename-case-changes

git bash ssh-keygen (same as linux)

wsl2 里 默认编辑器

https://stackoverflow.com/questions/2596805/how-do-i-make-git-use-the-editor-of-my-choice-for-commits

中文乱码:log commit信息, diff 文件内容, 文件名

https://stackoverflow.com/questions/22827239/how-to-make-git-properly-display-utf-8-encoded-pathnames-in-the-console-window

https://gist.github.com/xkyii/1079783/3e77453c05f6bcbce133fd0ba128686683f75bf8

win10有个Beta版本的区域utf-8设置,开了以后很多软件都是乱码,包括微信小程序开发工具,哎,

Chrome

account sync

default browser( https://github.com/da2x/EdgeDeflector )

firefox

国内外登录地址一个有.cn 一个没有,所以不同地址相同邮箱账号不同, 选择firefox 官方 developer 版本+ win x64 版本+English版本

https://www.mozilla.org/zh-CN/firefox/all/#product-desktop-developer

Python 被windows商店劫持

https://stackoverflow.com/questions/58754860/cmd-opens-window-store-when-i-type-python

閱讀全文 »

一点总结提炼

Thinking in react

https://reactjs.org/docs/thinking-in-react.html

  1. 从UI和后台获得设计稿和接口以及接口返回
  2. 单一功能原则 组件层级划分
  3. 编写静态html(无用户交互,官方上面没有,仅结构,无样式?)
  4. 转换成静态react(无用户交互,无state,仅展示,数据流均为父向子)
  5. 最小(且完整)的state状态,其它的内容全部由这些state产生, (例如保存数组,而不需要专门保存数组长度的变量), 主要完成的内容是 单向数据流与state,注意:父组件传递的不应该是state,随时间推移的不应该是state, 能由其它state计算得到的不应该存储
  6. 反向数据流

有一点不同的是, 真实开发中, 可能不会一个业务+组件同时提供. 思考顺序会变为,开发组件,再开发业务,而上面的思路其实也是有效的. 而对组件开发的思路比较像是, 先考虑出入参数,就像思考函数一样,考虑它的复用性以及编码迭代改版的设计性

props vs state

https://reactjs.org/docs/faq-state.html#what-is-the-difference-between-state-and-props

https://github.com/uberVU/react-guide/blob/master/props-vs-state.md

https://lucybain.com/blog/2016/react-state-vs-pros/

props: 外部传递给组件, 应该看成只读的, 传递回调函数

state: 组件内部自己管理, 初始状态主要由客户事件变化, setState异步(一般传递函数, 这是实现细节,注意官方文档说不要依赖这种实现,相关问题可以看最近发生的rust的二分搜索引发的问题(与react无关))

相同点: 纯js对象, 变化触发更新, 相同的(props,state)输入输出应该是恒定的(for就有问题?)

?? state不可由子组件修改, props, state不应太多?


就目前项目实践的感受,对于业务开发, 基本只有业务层关联的(顶层)需要state,而基础组件内都不持有state,主要是事件触发和props渲染.

setState 非同步的一些举例

https://github.com/facebook/react/issues/11527#issuecomment-360199710

即使 state同步,props是不同步的, react的props 是只读不会变的,和vue是对象引用不同

1
2
3
4
5
state触发
props 触发
state触发
props 触发 // 这种情况 可能是老的props,而真实的props或许已经更新了
state触发

为了说明不是理论上的,然后还翻出了多个历史issue

Flux 单向数据流

https://facebook.github.io/flux/docs/in-depth-overview

https://www.youtube.com/watch?v=nYkdrAPrdcw&list=PLb0IAmt7-GS188xDYE-u1ShQmFFGbrk0v&t=621s&ab_channel=FacebookDevelopers

https://facebook.github.io/flux/docs/overview

https://github.com/facebook/flux/tree/master/examples

https://code-cartoons.com/a-cartoon-guide-to-flux-6157355ab207

action -> dispatcher -> store -> view -> action

action: type + payload

dispatcher(同步), 可以waitFor 等待其它dispatcher

store: 数据储存 更新view

controller view & view: store变化被通知,让view把数据渲染

执行流程

初始state数据

state去dispatcher “订阅”?

controller view 从state获取数据,给view渲染

controller view 去store “订阅”?

用户事件

view 通知 action, 或其它Action

action 通知dispatcher

dispatcher 给对应的store

store内部处理,如果需要更新state,更新完以后,通知controller view , 根据 type 分发

controller view获取新state并通知view渲染

从根本上讲,仅从上述的角度来看, 根本在规范了流程和代码位置, 对于新进入的开发来说, 能直接通过代码,知道各个层级的内容. 本质上解决的更多是一个软件工程的质量管理工程经验得到解决方案, 甚至可以脱离redux到不同的框架内使用. 相对于MVC是 给MVC内部调用关系错综复杂的解决方案

从另一个角度讲, 只能通过触发Action来修改store里的内容

单元测试更容易!

从其它技术来讲,现在有rxjs, 直接描述关系式的开发,和vue这样的自己实现的reactive的关系式描述开发, 当然也有 rxjs + flux 的方案

不过facebook说他们的消息显示1 的更新问题,现在还有不少应用持续有这种bug :-)

然后看youtube视频上小姐姐的展示,实际上再回想开发, 这里的数据流的每一条线,实际上是多条线

redux

npx create-react-app redux-ts-demo --template redux-typescript

redux有可以与react无关单独使用的redux core, 也就是一个具体的flux实现?

官方的 Basic Example 里能看到

用户实现reducer, 提供 createStore(reducer), subscribe(fn), getState(), dispatch()

@reduxjs/toolkit

提供 createSlice(提供导出 acitions,reducer), configureStore(替代上面的 createStore, reducer可以单个或者store的map)

其它getState,subscribe,dispatch上面有的还是有

同样要用户实现reducer,不过写法可以像是操作state(之所以说像是,是因为它并不是真的更改,而是toolkit内Immer去做的变化检测一类,也要注意因为只有这里用Immer,所以只能在createSlice或createReducer里面这样写)

Redux Slices: A “slice” is a collection of Redux reducer logic and actions for a single feature in your app

这个角度是对root state的划分,

同时简化了reducer和actions的写法,并且在ts加持下能不再靠字符串(或额外自己写控制)保持一致

reducer规则:

  1. 计算结果应该只由old state和 action的传入相关(也就是不依赖 其它变量), 纯函数能让它”可以预测”易测试,更加工程友好
  2. 不修改现有state,而是产生新的修改后的state
  3. 不应该有异步或其它副作用

time-travel debugging 也是一个特点

thunks 异步, 直接使用是redux-thunk,现有的toolkit里configureStore能简化

外部是包裹,返回thunk函数, 中间是异步

内部是调用dispatch 和 getState

官方demo( async+await看起来就很自然舒服了, 对于外部调用thunk的包裹, 也是直接和同步调用看上去类似,fetchUserById()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// the outside "thunk creator" function
const fetchUserById = userId => {
// the inside "thunk function"
return async (dispatch, getState) => {
try {
// make an async call in the thunk
const user = await userAPI.fetchById(userId)
// dispatch an action when we get the response back
dispatch(userLoaded(user))
} catch (err) {
// If something went wrong, handle it here
}
}
}

import { useSelector, useDispatch } from 'react-redux' 可以 解决把redux中的state用在react里和调用redux里的dispatch ( 作为对比默认是useState

官方说, 还是通过使用react提供的useState和useEffect,来构建自己的逻辑

在ts demo生成的模板里是

1
2
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

官方export const selectCount = (state: RootState) => state.counter.value;

并且注释说,也可以不定义直接在使用的地方写useSelector((state) => state.counter.value)

你也可以不完全是暴露值,也可以是函数处理映射后const countPlusTwo = useSelector(state => state.counter.value + 2)

The answer is NO. Global state that is needed across the app should go in the Redux store. State that’s only needed in one place should be kept in component state.

提供了+1,+n,条件+2,异步+的四种demo

总结

redux基本是一个flux的实现

@reactjs/toolkit 简化了redux中 actions,reducer 编写和使用

thunk: 异步的

对比Vue

TODO 三个框架的工程解决方案和实际产出对比

  1. props, Vue也提供,但是没有强制限死为只读,导致接手的不少前端经理的代码还有修改props的行为, 而即使不是因为代码也会因为用户操作,没有对应的vm更改时,展示的页面不是”vm渲染的”, 最常见的就是input只传value
  2. Vue凡是要渲染的 都是data/computed/props, 但对于这种完全, 其实没有静态的划分,虽然可以通过const配置 + js语法引入到data里
  3. 回调和emit的区别就是需要忽略时的体验了
  4. flux/redux vs vuex, vuex(以我的视角vuex一定上是为了仿照其它框架的解决产物,其设计理念一定程度上和vue本身是相互违背的, vuex完成了简单共享js完成不了的动态触发(其实核心是调用了Vue.data), 提供了 mutation/actions 看上去划分了 同步异步, 也可以devtools调试, 也可以有插件支持,但就我看目前的开发来说,很多很多只是使用了 数据共享这一点, 而vue的简单设计理念,在我看来更适合直接等号而不是修改需要调用函数,+声明化的使用,剩下的就是module和这里无关, 所以vuex本质是一个vue reactive + flux 的类似实现)

延伸阅读

ts

Immer

开始

一般来说tutorial, 是从有什么讲什么的开始

这里我们假设一个已经能写js的,但只是搜索react的用法,然后就开始用,可能遇到的问题(坑?)

閱讀全文 »

安装

sudo apt install texlive-full

包还蛮大的,3GB下载,5GB展开

Interface zh_CN

Options->Configure TeXstudio->General->Language

Hello world

1
2
3
4
5
6
7
8
9
10
11
12
13
% !TEX program = xelatex
% !Mode:: "TeX:UTF-8"
%Save as UTF-8, run xelatex.

\documentclass{article}

\usepackage{xeCJK}

\begin{document}

hello,你好

\end{document}
閱讀全文 »

众所周知,我电脑目前只有16G,虽然Ubuntu本身吃不了多少内存,要同时开Dota2,VMware,Chrome之类的,gitlab/jenkins对我来说已经太大了

要想在自己本地电脑上玩个git server,(虽然也没啥卵用,毕竟一般来说私有的本地git就管理了不需要server,公开的都丢github

就要找一个小内存占用的

建立一个宿主上的存放gogs数据的文件夹,我这边数据类都丢在/data里,所以我这是mkdir -p /data/gogs

然后启动

1
docker run --name=gogs -p 10022:22 -p 10080:3000 -v /data/gogs:/data gogs/gogs

然后网页开localhost:10080就好了

这里需要注意的是git设置用[git@192.XXX.XXX.XXX:10022]:cromarmot/demo.git的形式

这样就可以dota2+vmware+chrome+nextcloud+gogs了,好耶,虽然也不知道有啥卵用

https://github.com/gogs/gogs/tree/main/docker

有些别人整理过的,暂时也不太需要二次整理的文章,建立索引,缺点是可能失效链接和无法自己维护, 移除用twitter书签的, 手机 Pocket, 网页书签栏,

文章

分布式文件系统的演化

GO 编程模式:K8S VISITOR 模式

proposals/enhanced-default-processor

ng packages

牛油果烤面包

捕蛇者说

ng ivy

美团技术团队

tidb

gnu core utils 源码

regex 填字游戏

stanford cs 329s 机器学习设计

ncnn

go语言设计与实现

Learning How to Learn

git

hacker101

typescript challenge

Sylvester Equation

pingcap database learning path

FB vector

logrotate

timing attack

py spider

The Aggregate Magic Algorithms

py command line

Modern MVVM iOS App Architecture with Combine and SwiftUI

k8s 调度

tsinghua aos

RUST语言的编程范式 coolshell

sourcetrail

cmu type theory

Abstract Algobra Youtube

kube ladder

Project Zero

0%