另一页

Git意外提交大文件如何处理

Git作为版本管理系统,仓库区自然保存有历史提交与历史文件。但远程Git仓库默认不提供LFS服务,如果不慎提交了大文件(像我提交了electron打包后的应用),远程仓库会拒绝同步。

因为Git是版本管理系统,所以删除文件不会影响仓库中的历史提交,修改历史提交有两种方法:

filter-branch

branch-filter对历史记录commit重新应用指定操作,如git filter-branch --tree-filter 'rm -f xxx.yyy' HEAD,将对第一个commit到HEAD应用。

tree-filter和index-filter都可以实现相同的目的,区别在于tree-filter将每一个commit checkout到临时文件夹并允许自定义命令,index-filter将commit复制到index(暂存区)并应用自定义命令,因此index-filter更快。

rebase

最早听说rebase是用于合并冲突,它会将其中一分支的记录应用到rebase参数的分支上,两分支的commit记录一前一后,按顺序解决冲突。这里rebase有另一种用法,即rebase -i,它会提供一个可编辑的commit记录,支持修改commit记录(合并commit、对commit执行命令、编辑commit信息)。比如说提交大文件后,我们可以合并提交大文件与删除大文件的记录,合并后历史记录中不再留下该文件的痕迹

Object.create(null)与 {}的区别

Object.create(null)创建了一个没有原型的对象。而{}继承自Object。

继承Object即继承了hasOwnProperty等属性。

Promise限流

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
function* getPromise() {
while (true) {
let r = Math.random() * 1000
yield new Promise((resolve) => {
setTimeout(() => { resolve() }, r)
}).then(() => { console.log('fin in ' + r, promiseCount, ++count, allPromise) })
}
}

let getP = getPromise()
let promiseCount = 0
const maxCount = 10
let allPromise = 100
let count = 0

function sendNextPromise() {

if (promiseCount < maxCount) {
promiseCount++
allPromise--
getP.next().value.then(() => {
promiseCount--

if (allPromise > 0) { sendNextPromise() }

})

}

}

for (let i = 0; i < 100; i++) {

sendNextPromise()
}

Context使用的一系列问题

Context API是React16推出的一种简单的组件间状态共享方式。在外层创建Context并用Provider包裹内层,在内层可以通过useContext或this.context访问数据。不需要通过props层层传递数据和回调函数,也避免引入Redux和更多复杂度。

遇到的问题:

A. 如果使用函数式组件并且将对象作为context,有可能不会触发更新。这种情况可以考虑给Provider传拷贝的对象而不只是一个引用。也可以改用class组件,因为其可以调用setState与forceUpdate方法。

B. 用Provider组件包裹下层class组件后,下层组件获取到的context为空。发现是我的理解错了,this.context不能自动获取,而需要先指定contextType。(useContext不用)

C. ReferenceError: Cannot access 'MyContext' before initialization

我一定是先创建context再引用,那么为什么会报错呢?我想到了import的问题。我在index.ts里引用app.ts,那么应该是app.ts先加载。这个报错给我上了一课,我才会去想JS如何去执行import。React要求contextType必须是static的,所以在import的时候就会执行,但我初始化MyContext的语句却写在index.ts里面(也就是import后面),导致import的时候读不到。如果我只在constructor里使用MyContext,就不会报这个错,因为constructor执行的时机更晚。React.render()渲染组件时,constructor才会真正触发(对象才真正被创建),那个时候import已经结束了。

D.context做修改后,子组件只有下次渲染才会访问新的context。也就是说不能在js中做更改后立即访问到修改,setTimeout也不行,因为context的引用变了。

React焦点管理

最普通的场景

设置元素的ref,获取引用,调用focus()方法

在不同层级的组件间管理

父组件需要获得子组件的引用,需要传给子组件回调,让父组件能够获得引用。

有列表的场景

一种方法是将所有子组件的ref传给父组件,这样需要一个ref数组。由于数组可能动态变化,不能用useRef hook,只能在class组件内实现。数组变化更新ref。

也可以只创建一个ReactRef。渲染列表时,会被聚焦的那一项被赋予ref,由父组件管理这个引用。

更新状态后如何处理按钮事件

我想实现一个需求:编辑框聚焦时按钮显示“完成”,编辑框失焦时显示“新任务”。看起来很好实现,但面临尴尬的问题:当我点击“完成”时,编辑框已经失焦,其实此时应该显示“新任务”了。所以,真实的逻辑应该加上:如果失焦时按钮没有被点击,那么显示“新任务”,如果因为按钮被聚焦而导致编辑框失焦,那么应该显示“完成”直到鼠标松开。

沿着这个思路非常绕,我想了很久都不知道如何处理。稍微理了一下,决定按钮是否显示的条件,不只有编辑框是否聚集,还有“刚才编辑框是否聚焦”。怎么理解“刚才”呢?难道要通过时间来设计逻辑吗?

我想还是要借助一下时间的逻辑。即blur事件触发后,状态在一定时间后变为false,如果按下的是按钮,则终止这个过程,由按钮变更状态。

如何理解一定时间?它可以是较短的时间。但设想一下,即便是50ms,也意味着在50ms内焦点变化没有任何响应。如果50ms内再次聚焦,问题就大了,因为在focus时触发的状态变化其实被丢弃了,这个概念有点类似数据库的丢失修改。会导致即使处于编辑状态,状态也为“非编辑”。

尽管50ms内失焦再聚焦的概率比较小,我们仍然需要考虑,有没有更好的方案?时间维度上,0ms是最好的,逻辑维度上,Blur事件、Focus事件后立即检查是最好的。其实可以做到,只是我们不能为每个元素都绑定Focus,我们需要在Blur里设置Focus后才能执行的操作。了解过JS异步机制就会知道,点击按钮导致焦点转移,这个过程先运行Blur再运行Focus,但两个操作都被放进了异步队列中,如果我们在Blur中设置setTimeout(()=>{},0),最后的执行顺序是Blur-Focus-setTimeout。我们实现了Focus后立即执行一些操作,却不必为所有元素绑定focus。

剩下的问题是如何判断Focus的元素。这一点DOM提供了activeElement的API,我们只需要为按钮添加某个独特的DOM属性,用于判断就好了。

最后我们整理一下。编辑框聚焦时状态变为Editing,焦点转移时触发编辑框的Blur,然后触发activeEleemnt的Focus,之后执行Blur添加的异步操作,依据activeElement判断状态是否置为NonEditing。如果activeElement是按钮,则不改变状态,等按钮Click触发后再改变。

补充:为什么要这样做?如果不对按钮的Click做特殊处理,那么点按钮的时候“完成”立即变成“新任务”,导致触发的onClick事件不符合预期。

不方便的TS报错

场景1

我想访问document.activeElement的属性,但ts提示我该类型上不存在那个属性。这简直是在扯淡,Element类型的属性不应该包括any吗?我向dom元素上添加了标记去哪里修改类型定义?我只能let document:any=window.document再访问activeElement的属性,真的很不方便。

Slider/Fullpage实现

实现思路

A.监听滚动,用JS替换元素内容。好处是渲染区域和可视区域一样大,不会浪费资源,但翻页效果不好做。

B.监听滚动,用JS控制translate变化。可视区域下,所有页面都被渲染。

遇到的问题

1.wheel事件太频繁,一次操作只应触发一次。我没有用setTimeout+clear的防抖做法,而是直接比较时间戳,属于节流。但就效果来说,两种方式都有问题。

防抖的意思是,多次频繁操作只在最后一次操作后触发一次。但Chrome(Safari已做优化)中wheel事件可能会持续接近1s,这种延迟几乎不可接受。

节流的意思是允许多次操作的第一次操作立即执行,之后一段时间都不允许再执行。这么做的后果是,1s内我都不能再划动第二次,这也不是我想要的。

我想要的效果是,初次事件立即触发,之后的事件忽略,下一次滑动时也能立即触发。

为此我做了一些优化,比如规定一个合理的忽略时间,还有,防止连续两次方向相同的滚动(因为这样很容易误判),但允许方向相反的滚动。以及forceUpdate,避免setState异步行为的延迟。

这些优化背后有一个问题,即我无法判断,在相同方向滚动两次时,究竟是上一次滚动剩下的事件,还是新的事件?不能通过滚动的程度来判断,因为用户可能轻轻一划,也可能划得很快很远。

很快,我想到了,一直以来,我用了一个React组件的一个属性来存储时间,但每个子组件都可以有自己的计时器。组件一不能连续滑动,但不影响组件二上的滑动。

举个例子,从页面1切换到页面2后,页面1在1.5s内都不会触发新事件,被视为滞后的wheel事件。但页面2可以继续滑向页面1或页面3。

分别计时虽然一定程度上区分了上一次滚动的事件和新手势的事件,但不完美。1.5s内,我有可能两次触发相同方向的滚动。

吃过晚饭,有力气思考了。我没有必要拘泥于首次之后1.5s内不触发,我可以结合防抖和节流,第一次事件发生就执行,但每次事件都会延后下一次执行的时机。

滑动效果演示:简历

hexo不能使用ftpsync

已经有人给出了分析,jsftp有问题并且没有人修,我选择不用这种方式同步了。

我比较倾向的替代方式,手动上传、sftp同步、CI、Rsync,我都接受但是CI之前还没有尝试过,比较有趣。

安装flow.ci

Vue组件的data属性为什么推荐用函数

第一层 官网推荐

因为官方文档上就是这么写的。

第二层 多组件共享引用

其实针对这个常见的问题,有很多人知道,如果同一份组件多次渲染,而JS传对象又只传引用,就会导致多组件使用同一份状态。

第三层 原因

上面的说法是对的。但是理论上使用导出的vue实例创建组件不会导致共享引用。一个文件直接export default一份对象字面量,看起来其他文件不会共享。但这只是看起来,实际上被编译成es5语法后,export default导出的对象直接变成普通对象,而非字面量。在es6环境,export deault对象字面量可能仍然是引用。

环境变量配置指南

因为安装grunt-cli后发现grunt命令不能使用,很明显没有配置环境变量。(grunt-cli也有点奇怪,yarn、vue-cli、webpack-cli还能正常用)

mac默认终端是zsh,其配置文件包括~/.bash_profile ~/.bash_rc ~/.profile 等。

编辑其中任何一个文件的内容,加上PATH=$PATH:/usr/bin/node/… 其中:作用是拼接,意味着path不会覆盖掉其他path的值。

12