易冬英的博客

小鱼儿与大前端


  • 首页

  • 分类

  • 归档

  • 关于

  • 站点地图

小鱼厨子养成记(一):7月

发表于 2018-07-15 | 阅读次数:

前言
作为一枚会撸码爱篮球会写作爱动漫的非二次元吃货程序媛,在前段时间的端午,回家吃了顿饭之后,感受到了来自老爸的厨艺鄙夷以及来自老妈的美味诱惑,于是决定开始我的厨艺生涯.
每周解锁一道新品种(哈哈,对于我来说,一切都是新品种)

为啥写博客?
写这个博客呢,只有两个目的:
1.记录一下自己的厨艺成长过程;(真相是,我这人比较懒,立个flag,鞭策一下自己)
2.记录每道菜的制作过程,随手拿来当菜谱用,哈哈!

先秀一波图,走你!

来自老妈的厨艺:
这里写图片描述

这里写图片描述

(原谅我手机的渣像素,实物比这好看)

啦啦啦啦,现在就开始我的厨艺表演了!

2018年7月1日 周日

今日午餐菜单: 海带排骨汤、啤酒鸭、素炒鱼丝、甜豆炒瘦肉,外加绿豆汤
新品解锁:啤酒鸭
材料准备:半片鸭、青椒、红椒、葱、姜、蒜、生抽、老抽、盐、八角、啤酒一瓶(只需要用半瓶,但没有半瓶卖,笑哭脸)
制作过程:
步骤一:
先把鸭子剁成块,焯水捞出;
葱姜蒜什么的切好,备用;
(至于切成什么形状,看个人爱好了, 我是把姜切片,辣椒切段了,葱忘记买了,然后辣椒籽弄掉)
步骤二:
炒锅里放油,油热了之后爆炒姜蒜辣椒,葱先不炒,因为熟得快,
然后把配料捞出,再倒点油,把鸭块倒入,中火炸一会,沥出鸭油,因为比较油腻,可以把油倒出一些;
步骤三:
放入盐、生抽、老抽,八角调味,倒入啤酒没过鸭块,半片鸭差不多半瓶的样子,
盖上锅盖,大火煮开,大约十分钟之后转小火慢炖,
把爆炒过的配料倒入,改大火翻炒,之后把葱放入,翻炒收干汁出锅!

大概就是这样了,上菜吧!

这里写图片描述

等等,打开方式不对,我好像忘记单独拍啤酒鸭了,
不过这么明显的三个菜,啤酒鸭没有不认识的吧?(左边第一盘)

那中间那盘是啥?

这个是特地从家乡带的鱼丝,啥》? 鱼丝你不认识? 出门右转找百度(https://baike.baidu.com/item/鱼丝/7386802?fr=aladdin) 。
说下这个菜的做法吧
大致是这样的:
1.烧开水把鱼丝煮软,沥水捞出备用;
2.姜蒜辣椒爆炒,用姜是因为有鱼腥味(但鱼丝里没有鱼,就跟鱼香肉丝没有鱼是一样的道理,憨笑脸)
3.把鱼丝倒入,加入盐,调味粉等,入味之后出锅装盘,搞定,就是这么简单!

第三盘的甜豆炒肉就没有说的必要啦!

再秀一张完整的图:
这里写图片描述

总结:炎炎夏日,啤酒配上鸭,绿豆配上汤,排骨配海带,超级享受!

2018年7月8日 周日

今日午餐菜单:海带海粉排骨汤、鸡蛋饼、辣椒炒肉
新品解锁:鸡蛋饼
材料准备:鸡蛋3个,面粉、葱、盐、水、植物油、醋
操作步骤:
1.香葱切细,备用,
用一个稍大一些的碗,倒入一定量的面粉,然后把鸡蛋磕进去,缓缓倒入清水,加入食盐和葱花;

2.用汤匙搅拌均匀,打成可以流淌的面糊状;这一步很关键,不能有面粉结块的现象

3.在锅中倒入植物油,先不加热,用汤匙装一匙面糊淋入锅内,
迅速转动锅子,将粉浆水平摊在锅底成圆饼状,

4.将锅子移到火上,用小火加热至面糊凝固成型,再翻面用小火煎另一面,直到两面都变得有鞋焦黄上色,出锅!

好了,上菜!
这里写图片描述

这里写图片描述

额额,只能说还凑合吧,毕竟葱花最后用的青椒代替!(手动表情-捂脸笑)

这个汤,我单独说一下,
里面放了海粉(我也不确定叫法,从家里带的),这个海粉,下火的,夏天炖汤喝贼爽,滑溜溜的。
为了看清原料,我单独来一张图:
这里写图片描述

诺,就是那个看起来有点点褐黄色的东西,老妈心疼我特别容易上火,走之前给我塞包里,也不知道这边有没有得买。

总结:说啥好呢?这周貌似有点清淡啊!

2018年7月15日 周日

今日早餐菜单:优酸乳 三明治
今日午餐菜单:红烧鱼块 油炸茄子 凉拌萝卜 水果捞
早餐的三明治呢,做法很简单
就是稍微煎一下切片面包,煎一个鸡蛋,煎两片火腿片,生菜忘记放了,夹好,搞定!
直接上图吧!
这里写图片描述
冰过的牛奶,口感俱佳!

今日新品一:红烧鱼块
材料准备:鱼块、葱、姜、蒜、青椒、红椒、黄酒、盐、五香粉、淀粉,酱油
制作过程:
步骤一:
鱼块洗净切好,放入少许淀粉,五香粉和盐,拌均匀。
步骤二:
将姜蒜辣椒爆炒后装盘,备用;
步骤三:
倒入油,烧热油锅后将鱼块倒入,大火炸几分钟,炸至两边带点焦黄,倒入少许黄酒,酱油、调味粉进行调味,
倒入半碗水,盖上锅盖,大火焖几分钟;
步骤四:
将姜蒜辣椒、葱倒入,进行翻炒入味收汁,出锅!
注意:翻炒时不要将鱼块翻烂,最好是翻一遍就出锅。
上菜吧!
这里写图片描述

略微有点翻烂了,,卖相不咋地,不过味道还可以啦!

今日新品二:油炸茄子
材料准备:茄子一条,面粉,淀粉、鸡蛋、盐、酱油
制作过程:
步骤一:
将茄子切条,放在冷水中泡大概十分钟左右;
步骤二:
将茄子捞出,加入少许盐和酱油搅拌,腌制片刻让茄子入味;
步骤三:
取一只大碗,将面粉倒入,再加入少许淀粉,打入一个鸡蛋,加一些水搅拌,调成面糊备用;
步骤四:
锅内倒入油,油锅至七八成热时(表面有小气泡翻滚),将腌好的茄子滚在面糊上,下锅内炸,翻面炸至两面焦黄夹出装盘即可!
小提示:
1.腌制师可以加入胡椒粉、辣椒粉等调味料,随你喜欢;
2.炸的时候不宜太大火,中火即可,记得翻面,免得烧焦;
3.调面糊为啥加鸡蛋?因为加鸡蛋可以更脆更香。

嗯,就酱,上菜吧!
这里写图片描述

然后是午餐完整图来一张:
这里写图片描述

最后再上几张图,都是日常操作,记不得哪一天的了, 哈哈!
这里写图片描述

这里写图片描述

这里写图片描述

深入浅出ES6(一): 你真的了解箭头函数吗

发表于 2018-02-05 | 分类于 深入浅出ES6 | 阅读次数:

前言
这个系列主要是说明ES6的新特性,从2015年到现在,es6出来也有挺长一段时间了,在项目中也在普遍使用这些特性.,网上的写es6的文章也大把, 但我感觉可能还是停留在会用的阶段,,至于为什么要这么用, 又为什么会出现这个特性,解决了什么样的问题,这些都有些一知半解. 所以,打算抽时间去了解es6未知的一面.
这篇文章,就先从用的最多的箭头函数开始->

箭头函数的由来
为什么叫箭头函数?
因为,它的定义用的是一个箭头.
那为什么要用一个箭头?
先来看一段代码:

1
2
3
4
5
<script language="javascript">
<!--
document.bgColor = "brown"; // red
// -->
</script>

有没觉得很奇怪?在注释里边的代码也有效?当然,如果知道注释风格的代码的人,应该可以理解.

箭头符号在JavaScript诞生时就已经存在,当初第一个JavaScript教程曾建议在HTML注释内包裹行内脚本,这样可以避免不支持JS的浏览器误将JS代码显示为文本。

老式浏览器会将这段代码解析为两个不支持的标签和一条注释,只有新式浏览器才能识别出其中的JS代码。

为了支持这种奇怪的hack方式,浏览器中的JavaScript引擎将<!--这四个字符解析为单行注释的起始部分,我没开玩笑,这自始至终就是语言的一部分,直到现在仍然有效, 这种注释符号不仅出现<script>标签后的首行,在JS代码的每个角落你都有可能见到它,甚至在Node中也是如此。

碰巧,这种注释风格首次在ES6中被标准化了,但在新标准中箭头被用来做其它事情。

箭头序列–>同样是单行注释的一部分。古怪的是,在HTML中–>之前的字符是注释的一部分,而在JS中–>之后的部分才是注释。

你一定感到陌生的是,只有当箭头在行首时才会注释当前行。这是因为在其它上下文中,–>是一个JS运算符:“趋向于”运算符!

function countdown(n) {
while (n –> 0) // “n goes to zero”
alert(n);
blastoff();
}

上面这段代码可以正常运行,循环会一直重复直到n趋于0,这当然不是ES6中的新特性,它只不过是将两个你早已熟悉的特性通过一些误导性的手段结合在一起。你能理解么?通常来说,类似这种谜团都可以在Stack Overflow上找到答案。

当然,同样地,小于等于操作符<=也形似箭头,你可以在JS代码、隐藏的图片样式中找到更多类似的箭头,但是我们就不继续寻找了,你应该注意到我们漏掉了一种特殊的箭头。
当然,同样地,小于等于操作符<=也形似箭头,你可以在JS代码、隐藏的图片样式中找到更多类似的箭头,但是我们就不继续寻找了,你应该注意到我们漏掉了一种特殊的箭头。

<!-- 单行注释
--> “趋向于”操作符
<= 小于等于
=> 这又是什么?
=> 到底是什么?我们今天就来一探究竟。

首先,我们谈论一些有关函数的事情。


函数表达式无处不在
JavaScript中有一个有趣的特性,无论何时,当你需要一个函数时,你都可以在想添加的地方输入这个函数。

举个例子,假设你尝试告诉浏览器用户点击一个特定按钮后的行为,你会这样写:

$("#confetti-btn").click(

jQuery的.click()方法接受一个参数:一个函数。没问题,你可以在这里输入一个函数:

$("#confetti-btn").click(function (event) {
  playTrumpet();
  fireConfettiCannon();
});

对 于现在的我们来说,写出这样的代码相当自然,而回忆起在这种编程方式流行之前,这种写法相对陌生一些,许多语言中都没有这种特性。1958年,Lisp首 先支持函数表达式,也支持调用lambda函数,而C++,Python、C#以及Java在随后的多年中一直不支持这样的特性。

现在截然不同,所有的四种语言都已支持lambda函数,更新出现的语言普遍都支持内建的lambda函数。我们必须要感谢JavaScript和早期的JavaScript程序员,他们勇敢地构建了重度依赖lambda函数的库,让这种特性被广泛接受。

令人伤感的是,随后在所有我提及的语言中,只有JavaScript的lambda的语法最终变得冗长乏味。

// 六种语言中的简单函数示例
function (a) { return a > 0; } // JS
[](int a) { return a > 0; }  // C++
(lambda (a) (> a 0))  ;; Lisp
lambda a: a > 0  # Python
a => a > 0  // C#
a -> a > 0  // Java



箭袋中的新羽
ES6中引入了一种编写函数的新语法

// ES5
var selected = allJobs.filter(function (job) {
  return job.isSelected();
});
// ES6
var selected = allJobs.filter(job => job.isSelected());

当你只需要一个只有一个参数的简单函数时,可以使用新标准中的箭头函数,它的语法非常简单:标识符=>表达式。你无需输入function和return,一些小括号、大括号以及分号也可以省略。

(我个人对于这个特性非常感激,不再需要输入function这几个字符对我而言至关重要,因为我总是不可避免地错误写成functoin,然后我就不得不回过头改正它。)

如果要写一个接受多重参数(也可能没有参数,或者是不定参数、默认参数、参数解构)的函数,你需要用小括号包裹参数list。

// ES5
var total = values.reduce(function (a, b) {
  return a + b;
}, 0);
// ES6
var total = values.reduce((a, b) => a + b, 0);

我认为这看起来酷毙了。

正如你使用类似Underscore.js和Immutable.js这样的库提供的函数工具,箭头函数运行起来同样美不可言。事实上,Immutable的文档中的示例全都由ES6写成,其中的许多特性已经用上了箭头函数。

那么不是非常函数化的情况又如何呢?除表达式外,箭头函数还可以包含一个块语句。回想一下我们之前的示例:

// ES5
$("#confetti-btn").click(function (event) {
  playTrumpet();
  fireConfettiCannon();
});

这是它们在ES6中看起来的样子:

// ES6
$("#confetti-btn").click(event => {
  playTrumpet();
  fireConfettiCannon();
});

这是一个微小的改进,对于使用了Promises的代码来说箭头函数的效果可以变得更加戏剧性,}).then(function (result) { 这样的一行代码可以堆积起来。

注意,使用了块语句的箭头函数不会自动返回值,你需要使用return语句将所需值返回。

小提示:当使用箭头函数创建普通对象时,你总是需要将对象包裹在小括号里。

// 为与你玩耍的每一个小狗创建一个新的空对象
var chewToys = puppies.map(puppy => {});   // 这样写会报Bug!
var chewToys = puppies.map(puppy => ({})); //

用小括号包裹空对象就可以了。

不幸的是,一个空对象{}和一个空的块{}看起来完全一样。ES6中的规则是,紧随箭头的{被解析为块的开始,而不是对象的开始。因此,puppy => {}这段代码就被解析为没有任何行为并返回undefined的箭头函数。

更令人困惑的是,你的JavaScript引擎会将类似{key: value}的对象字面量解析为一个包含标记语句的块。幸运的是,{是唯一一个有歧义的字符,所以用小括号包裹对象字面量是唯一一个你需要牢记的小窍门。




箭头函数与this


既然要说箭头函数, 自然避不开this了
了解es5的人,应该知道, 在不同地方使用this,它的指向也有所不同, 但总归来讲, 实际上this总是指向最后调用它的对象.

举个例子:

1
2
3
4
5
6
7
8
9
10
11
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); //undefined
console.log(this); //window }
}
}
var j = o.b.fn;
j();

上面这个例子中,fn函数始终没有执行,直到执行j();而j()又是通过window来调用,所以最后输出的this是window.
总的来说, this的调用方式和指向有以下几种:

1
2
3
4
5
6
函数调用方式和this指向:
(1)直接调用:函数内部this指向全局window
(2) 通过对象使用点来调用:函数内部this指向调用对象
(3) 触发事件调用函数:函数内部this指向调用触发事件的对象
(4) 以new的方式来调用:函数内部this指向本次函数执行时对应的一个匿名对象。
(5) 通过call的方法来间接调用方法:函数内部this指向call方法的第一个参数(自己指定this)。

这篇文章的目的不是要解析this, 如果对this不清楚的童鞋,可以参考这篇文档: this 指向详细解析(箭头函数)

看到this在这么多调用方式下的指向都不同, 有没有被绕晕? 相信肯定也有很多童鞋跟我一样,踩过this的坑. 而箭头函数的存在, 就避免了这种指向问题,在箭头函数中, this总是指向词法作用域, 也就是外层的调用者.

看个例子:

1
2
3
4
5
6
7
8
9
10
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = function () {
return new Date().getFullYear() - this.birth; // this指向window或undefined
};
return fn();
}
};

上面这个例子中,fn()并没有得到我们预期的日期, 而是出错,因为在fn()函数中,this.birth并不是指向当前obj,而是指向函数的调用者window, 因此birth是undefined,

现在让我们用箭头函数来改造这个函数:

1
2
3
4
5
6
7
8
9
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
return fn();
}
};
obj.getAge(); // 27

在箭头函数中, this指向的是外层调用者obj,因此最后拿到了birth这个参数.

另外,在箭头函数中,我们不再需要这种hack写法:

1
var that = this;

关于箭头函数的案例
上面应该大致说明白了箭头函数吧?
那么现在,就来说说它的使用.


改造开始


首先,我们从一个例子开始,在 ES5 中,我们一般是这么书写的。

var sum1 = function(num1, num2) { return num1 + num2; };

那么,改造成箭头函数,它是什么样子呢?

var sum2 = (num1, num2) => { return num1 + num2;};

小括号内的参数列表和花括号内的代码被 => 分隔开了。这个就是箭头函数的魅力,箭头函数使得表达更加简洁,从而简化了我们的代码。

如果一个表达式的代码块, 只是 return 后面跟一个表达式,那么还可以进一步简化。

var sum3 = (num1, num2) => num1 + num2;

如果某个方法只含有一个参数。

console.info(“=> ES5 写法”);
var curf1 = function(v) {
return v;
};

我们甚至可以省略小括号。

console.info(“=> ES6 写法”);
var curf2 = v => v;

如果某个方法没有参数。

console.info(“=> ES5 写法”);
var f1 = function() {
return “梁桂钊”;
};

我们仍可以提供一对空的小括号,如同不含参数的

console.info(“=> ES6 写法”);
var f2 = () => “梁桂钊”;

补充一个例外,如果箭头函数直接返回一个对象,必须在对象外面加上括号。

1
2
3
4
5
6
7
8
9
10
11
12
console.info("=> ES5 写法");
var f3 = function() {
return {
real_name: "梁桂钊",
nick_name: "LiangGzone"
}
};
console.log(f3());

console.info("=> ES6 写法");
var f4 = () => ({real_name: "梁桂钊",nick_name: "LiangGzone"});
console.log(f4());



关于解构
我们还可以使用到 ES6 解构赋值特性。ES5 写法,之前是这样的。

1
2
3
var f5 = function(person) {
return person.first + ' ' + person.last;
}

使用到 ES6 解构赋值特性后,就更加好理解了。

1
const f6 = ({ first, last }) => first + ' ' + last;

关于回调函数
我们经常使用回调函数,之前的常规的做法。

1
2
3
4
5
console.info("=> ES5 写法");
var x1 = [1,2,3].map(function (x) {
return x * x;
});
console.info(x1);

那么,现在我们可以进行改造。

1
2
3
console.info("=> ES6 写法");
var x2 = [1,2,3].map(x => x * x);
console.info(x2);

rest参数结合

没有使用箭头函数,之前,我们的代码可能长这样子。

1
2
3
4
5
console.info("=> ES5 写法");
var x3 = function(...nums){
return nums;
}
console.info(x3(512, 1024));

那么,现在我们可以进行改造。

1
2
3
console.info("=> ES6 写法");
var x4 = (...nums) => nums;
console.info(x4(512, 1024));

那么, 我们什么时候会用到箭头函数呢?
ES6 的箭头函数在微软的新版本中有使用, 他们也在 Babel,Traceur,和 TypeScript 得到实现, 等等

以上就是我对箭头函数的一点点见解, 如果有不对的地方, 欢迎指正!

参考文档:
(1) 深入浅出ES6(七):箭头函数 Arrow Functions:
https://blog.csdn.net/hqh642134542/article/details/78809951
(2).你看懂“箭头函数”了么?
https://www.cnblogs.com/libin-1/p/5995457.html
(3).this指向详细解析(箭头函数):
https://www.cnblogs.com/dongcanliang/p/7054176.html
(4) 极客学院-箭头函数:
https://wiki.jikexueyuan.com/project/es-six-deeply/arrow-functions.html
(5) 廖雪峰-箭头函数:
https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001438565969057627e5435793645b7acaee3b6869d1374000
(6) MDN-箭头函数:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions
(6) 阮一峰-箭头函数issue:
https://github.com/ruanyf/es6tutorial/issues/150

关于React setState的实现原理(三).md

发表于 2017-09-10 | 分类于 react的概念理解 | 阅读次数:

上一篇文章中提到事务即将结束时,会去调用FLUSH_BATCHED_UPDATES的flushBatchedUpdates方法执行批量更新,该方法会去遍历dirtyComponents,对每一项执行performUpdateIfNecessary方法,该方法代码如下:

1
2
3
4
5
6
7
8
9
performUpdateIfNecessary: function (transaction) {
if (this._pendingElement != null) {
ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);
} else if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
} else {
this._updateBatchNumber = null;
}
}

在我们的setState更新中,其实只会用到第二个 this._pendingStateQueue !== null 的判断,即如果_pendingStateQueue中还存在未处理的state,那就会执行updateComponent完成更新。
那_pendingStateQueue是何时被处理的呢,继续看!

通过翻阅updateComponent方法,我们可以知道_pendingStateQueue是在该方法中由_processPendingState(nextProps, nextContext)方法做了一些处理,该方法传入两个参数,新的props属性和新的上下文环境,这个上下文环境可以先不用管。我们看看_processPendingState的具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
_processPendingState: function (props, context) {
var inst = this._instance; // _instance保存了Constructor的实例,即通过ReactClass创建的组件的实例
var queue = this._pendingStateQueue;
var replace = this._pendingReplaceState;
this._pendingReplaceState = false;
this._pendingStateQueue = null;
if (!queue) {
return inst.state;
}
if (replace && queue.length === 1) {
return queue[0];
}
var nextState = _assign({}, replace ? queue[0] : inst.state);
for (var i = replace ? 1 : 0; i < queue.length; i++) {
var partial = queue[i];
_assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
}
return nextState;
},

     什么replace啊什么的都可以暂时不用看,主要先看for循环内部做的事情,replace我们暂时认为是false。
for循环遍历了_pendingStateQueue中所有保存的状态,对于每一个状态进行处理,处理时首先判断保存的是function还是object。若是function,就在inst的上下文中执行该匿名函数,该函数返回一个代表新state的object,然后执行assign将其与原有的state合并;若是object,则直接与state合并。


注意,传入setState的第一个参数如果是function类型,我们可以看到,其第一个参数nextState即表示更新之前的状态;第二个参数props代表更新之后的props,第三个context代表新的上下文环境。之后返回合并后的state。


这里还需要注意一点,这一点很关键,代码中出现了this._pendingStateQueue = null这么一段,这也就意味着dirtyComponents进入下一次循环时,执行performUpdateIfNecessary不会再去更新组件,这就实现了批量更新,即只做一次更新操作,React在更新组件时就是用这种方式做了优化。

     好了,回来看我们的案例,当我们传入函数作为setState的第一个参数时,我们用该函数提供给我们的state参数来访问组件的state。该state在代码中就对应nextState这个值,这个值在每一次for循环执行时都会对其进行合并,因此第二次执行setState,我们在函数中访问的state就是第一次执行setState后已经合并过的值,所以会打印出2。然而直接通过this.state.count来访问,因为在执行对_pendingStateQueue的for循环时,组件的update还未执行完,this.state还未被赋予新的值,其实了解一下updateComponent会发现,this.state的更新会在_processPendingState执行完执行。所以两次setState取到的都是this.state.count最初的值0,这就解释了之前的现象。其实,这也是React为了解决这种前后state依赖但是state又没及时更新的一种方案,因此在使用时大家要根据实际情况来判断该用哪种方式传参。

接下来我们再来看看setState的第二个参数,回调函数,它是在什么时候执行的。

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
class Root extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}

componentDidMount() {
let me = this;
setTimeout(function() {
me.setState({count: me.state.count + 1}, function() {
console.log('did callback');
});
console.log('hello');
}, 0);
}

componentDidUpdate() {
console.log('did update');
}

render() {
return <h1>{this.state.count}</h1>
}
}

这个案例控制台打印顺序是怎样的呢?
不卖关子了,答案是did update,did callback,hello。这里是在一个setTimeout中执行了setState,因此其处于一个单独的事务之中,所以hello最后打印容易理解。然后我们来看看setState执行更新时做了些啥。前面我们知道在执行完组件装载即调用了componentDidMount之后,事务开始执行一系列close方法,这其中包括调用FLUSH_BATCHED_UPDATES中的flushBatchedUpdates,我们来看看这段代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var flushBatchedUpdates = function () {
// ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents
// array and perform any updates enqueued by mount-ready handlers (i.e.,
// componentDidUpdate) but we need to check here too in order to catch
// updates enqueued by setState callbacks and asap calls.
while (dirtyComponents.length || asapEnqueued) {
if (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
transaction.perform(runBatchedUpdates, null, transaction); // 处理批量更新
ReactUpdatesFlushTransaction.release(transaction);
}

if (asapEnqueued) {
asapEnqueued = false;
var queue = asapCallbackQueue;
asapCallbackQueue = CallbackQueue.getPooled();
queue.notifyAll(); // 处理callback
CallbackQueue.release(queue);
}
}
};

可以看我做了中文标注的两个地方,这个方法其实主要就是处理了组件的更新和callback的调用。组件的更新发生在runBatchedUpdates这个方法中,下面的queue.notifyAll内部其实就是从队列中去除callback调用,因此应该是先执行完更新,调用componentDidUpdate方法之后,再去执行callback,就有了我们上面的结果。


总结
React在组件更新方面做了很多优化,这其中就包括了上述的批量更新。在componentDidMount中执行了N个setState,如果执行N次更新是件很傻的事情。React利用其独特的事务实现,做了这些优化。正是因为这些优化,才造成了上面见到的怪现象。还有一点,再使用this.state时一定要注意组件的生命周期,很多时候在获取state的时候,组件更新还未完成,this.state还未改变,这是很容易造成bug的一个地方,要避免这个问题,需要对组件生命周期有一定的了解。

关于React setState的实现原理(二).md

发表于 2017-09-08 | 分类于 react的概念理解 | 阅读次数:

上一篇文章中,写到了关于Batch Update的实现,有不懂的童鞋可以回头看看 上一篇文章
React中的Transaction
大家学过sql server的都知道我们可以批量处理sql语句,原理其实都是基于上一篇我们说的Datch Update机制。当所有的操作均执行成功,才会执行修改操作;若有一个操作失败,则执行rollback(回滚)。

在React中,我们介绍过事件会在函数前后执行自己的逻辑,具体就是调用perform方法进入一个事件,这个方法会传入一个method参数。执行perform时先执行initializeAll方法按照一定顺序执行一系列的initialize操作,然后执行传入的method,method执行完后,就执行closeAll方法按照一定顺序执行一系列的close操作。注意一种事件不能同时开启,否则会抛出异常。给一个例子是实现事件:

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
var Transaction = require('./Transaction');

// 我们自己定义的 Transaction
var MyTransaction = function() {
// do sth.
};

Object.assign(MyTransaction.prototype, Transaction.Mixin, {
getTransactionWrappers: function() {
return [{
initialize: function() {
console.log('before method perform');
},
close: function() {
console.log('after method perform');
}
}];
};
});

var transaction = new MyTransaction();
var testMethod = function() {
console.log('test');
}
transaction.perform(testMethod);

// before method perform
// test
// after method perform

具体到实现上,React 中的 Transaction 提供了一个 Mixin 方便其它模块实现自己需要的事务。而要使用 Transaction 的模块,除了需要把 Transaction 的 Mixin 混入自己的事务实现中外,还需要额外实现一个抽象的 getTransactionWrappers 接口。这个接口是 Transaction 用来获取所有需要封装的前置方法(initialize)和收尾方法(close)的,因此它需要返回一个数组的对象,每个对象分别有 key 为 initialize 和 close 的方法。

当然在实际代码中 React 还做了异常处理等工作,这里不详细展开。有兴趣的同学可以参考源码中 Transaction 实现。

组件调用ReactDOM.render()之后,会执行一个_renderNewRootComponent的方法,大概是该方法执行了一个ReactUpdates.batchedUpdates()。 那么batchedUpdates是什么呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var transaction = new ReactDefaultBatchingStrategyTransaction();

var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,

/**
* Call the provided function in a context within which calls to `setState`
* and friends are batched such that components aren't updated unnecessarily.
*/
batchedUpdates: function (callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;

ReactDefaultBatchingStrategy.isBatchingUpdates = true;

// The code is written this way to avoid extra allocations
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
return transaction.perform(callback, null, a, b, c, d, e);
}
}
};

从代码中可以看出,这个batchedUpdate是第一次调用alreadyBatchingUpdates是false(储存起来了),回去执行transaction.perform(method)(前边说过perform执行会进入一个时间),这样就进入第一个事务,

这个事务是啥我们现在不用管,我们只需要知道这个transaction是ReactDefaultBatchingStrategyTransaction的实例,它代表了其中一类事务的执行。然后会执行method方法,就会进行组件的首次装载。完成后会调用

componentDidMount(注意,此时还是在执行method方法,事务还没结束,事务只有在执行完method后执行一系列close才会结束),在该方法中,我们调用了setState,出现了一系列奇怪的现象。因此,我们再来看看

setState方法:

1
2
3
4
5
6
7
ReactComponent.prototype.setState = function (partialState, callback) {
!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? "development" !== 'production' ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : _prodInvariant('85') : void 0;
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};

setState在调用时做了两件事,第一,调用enqueueSetState。该方法将我们传入的partialState添加到一个叫做_pendingStateQueue的队列中去存起来,然后执行一个enqueueUpdate方法。


第二,如果存在callback就调用enqueueCallback将其存入一个_pendingCallbacks队列中存起来。然后我们来看enqueueUpdate方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function enqueueUpdate(component) {
ensureInjected();

// Various parts of our code (such as ReactCompositeComponent's
// _renderValidatedComponent) assume that calls to render aren't nested;
// verify that that's the case. (This is called by each top-level update
// function, like setState, forceUpdate, etc.; creation and
// destruction of top-level components is guarded in ReactMount.)

if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}

dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}



上面的代码中,这个batchingStrategy就是上面的ReactDefaultBatchingStrategy,只是它通过inject的形式对其进行赋值,比较隐蔽。因此,我们当前的setState已经处于了这一类事务之中,isBatchingUpdates已经被置为true,所以将会把它添加到dirtyComponents中,在某一时刻做批量更新。


因此在前两个setState中,并没有做任何状态更新,以及组件更新的事,而仅仅是将新的state和该组件存在了队列之中,因此两次都会打印出0,我们之前的第一个问题就解决了,还有一个问题,我们接着往下走。


在setTimeout中执行的setState打印出了2和3,有了前面的铺垫,我们大概就能得出结论,这应该就是因为这两次setState分别执行了一次完整的事务,导致state被直接更新而造成的结果。那么问题来了,为什么setTimeout中的setState会分别执行两次不同的事务?之前执行ReactDOM.render开启的事务在什么时候结束了?我们来看下列代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function () {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
}
};

var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};

var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

function ReactDefaultBatchingStrategyTransaction() {
this.reinitializeTransaction();
}

_assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
getTransactionWrappers: function () {
return TRANSACTION_WRAPPERS;
}
});

这段代码也是写在ReactDefaultBatchingStrategy这个对象中的。我们之前提到这个事务中transaction是ReactDefaultBatchingStrategyTransaction的实例,这段代码其实就是给该事务添加了两个在事务结束时会被调用的close方法。即在perform中的method执行完毕后,会按照这里数组的顺序[FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES]依次调用其close方法。


FLUSH_BATCHED_UPDATES是执行批量更新操作。RESET_BATCHED_UPDATES我们可以看到将isBatchingUpdates变回false,即意味着事务结束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function enqueueUpdate(component) {
ensureInjected();
// Various parts of our code (such as ReactCompositeComponent's
// _renderValidatedComponent) assume that calls to render aren't nested;
// verify that that's the case. (This is called by each top-level update
// function, like setState, forceUpdate, etc.; creation and
// destruction of top-level components is guarded in ReactMount.)
if (!batchingStrategy.isBatchingUpdates) { //上一个事件结束执行过isBatchedUpdates=false,所以进入if中
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}



接下来再调用setState时(在setTimeout中,前文说过一步操作不会在主线程,我理解是在主线程结束才会执行,此时的主线程事件已经结束),enqueueUpdate不会再将其添加到dirtyComponents中,而是执行batchingStrategy.batchedUpdates(enqueueUpdate, component)开启一个新事务。


但是需要注意,这里传入的参数是enqueueUpdate,即perform中执行的method为enqueueUpdate,而再次调用该enqueueUpdate方法会去执行dirtyComponents那一步。这就可以理解为,处于单独事务的setState也是通过将组件添加到dirtyComponents来完成更新的,只不过这里是在enqueueUpdate执行完毕后立即执行相应的close方法完成更新,而前面两个setState需在整个组件装载完成之后,即在componentDidMount执行完毕后才会去调用close完成更新。总结一下4个setState执行的过程就是:先执行两次console.log,然后执行批量更新,再执行setState直接更新,执行console.log,最后再执行setState直接更新,再执行console.log,所以就会得出0,0,2,3。


到现在上面的问题已经解决,但是又出现一个新问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Root extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
let me = this;
me.setState({
count: me.state.count + 1
});
me.setState({
count: me.state.count + 1
});
}
render() {
return (
<h1>{this.state.count}</h1> //页面中将打印出1
)
}
}
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
class Root extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
let me = this;
me.setState(function(state, props) {
return {
count: state.count + 1
}
});
me.setState(function(state, props) {
return {
count: state.count + 1
}
});
}
render() {
return (
<h1>{this.state.count}</h1> //页面中将打印出2
)
}
}



这两种写法,一个是在setState中传入了object,一个是传入了function,却得到了两种不同的结果,这是什么原因造成的,这就需要我们去深入了解一下进行批量更行时都做了些什么。
关于这个部分, 可以看看我的下一篇文章

关于React setState的实现原理(一).md

发表于 2017-09-05 | 分类于 react的概念理解 | 阅读次数:

前言

首先在学习react的时候就对setSate的实现有比较浓厚的兴趣,那么对于下边的代码,可以快速回答吗?

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
class Root extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
let me = this;
me.setState({
count: me.state.count + 1
});
console.log(me.state.count); // 打印
me.setState({
count: me.state.count + 1
});
console.log(me.state.count); // 打印
setTimeout(function(){
me.setState({
count: me.state.count + 1
});
console.log(me.state.count); // 打印
}, 0);
setTimeout(function(){
me.setState({
count: me.state.count + 1
});
console.log(me.state.count); // 打印
}, 0);
}
render() {
return (
<h1>{this.state.count}</h1>
)
}
}

这段代码大家可能在很多地方看见过,结果是让你匪夷所思的0,0,2,3。 大部分人相信都不知道其中的原因,首先肯定会问:

为什么前两次为零,而加上setTimeout就可以打印出来?
为什么setTimeout打印出不同的结果?
那么请你接下来向下看,我首先说一下Batch Updata(批量更新)。如下图:
这里写图片描述

什么是Batch Update ?
在一些MV*框架中,就是将一段时间内对model的修改批量更新到view的机制。比如那前端比较火的React、vue为例。

在React中,我们在componentDidMount生命周期连续调用SetState:

1
2
3
4
5
componentDidMount () {
this.setState({ foo: 1 })
this.setState({ foo: 2 })
this.setState({ foo: 3 })
}

在没有Batch Update的情况下,上面的操作会导致三次组件渲染,但是使用Batch Update机制下时间上只运行了一次渲染。componentDidMount中三次对model的操作被优化为一次view更新,

不必要的Vitual Dom计算被忽略,从而提高了框架的效率。

Batch Update的实现
我们想到的可能就是数据结构中的栈和队列,比较一下还是使用一个queue来保存update,并在合适的时机对这个queue进行flush操作。那么现在有两个问题:

什么时候创建这个queue
什么时候对这个queue进行flush
那么我们要对Reac和Vue的源码进行分析,首先React:React中的Batch Update是通过Transaction(事务)来实现的。在React源码关于Transaction的部分可以用一幅画解释:

这里写图片描述

Transaction对一个函数进行包装,让React有机会在一个函数执行前和执行后运行特定的逻辑,从而完成对整个Batch Update流程的控制。

简单的说就是在要执行的函数中用事务包裹起来,在函数执行前加入initialize阶段,函数执行,最后执行close阶段。那么Batch Update中

在事件initialize阶段,一个update queue被创建。在事件中调用setState方法时,状态不会被立即调用,而是被push进Update queue中。

函数执行结束调用事件的close阶段,Update queue会被flush,这事新的状态才会被应用到组件上并开始后续的Virtual DOM更新,biff算法来对

model更新。

对比于React,Vue实现Batch update就简单多了:直接借助JS中的Event Loop。(参考阮老师的https://www.ruanyifeng.com/blog/2013/10/event_loop.html)

Vue中的核心代码就仅仅20多行,如下:

// https://github.com/vuejs/vue/blob/dev/src/core/observer/scheduler.js#L122-L148

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
/**
* Push a watcher into the watcher queue.
* Jobs with duplicate IDs will be skipped unless it's
* pushed when the queue is being flushed.
*/
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true
if (!flushing) {
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
nextTick(flushSchedulerQueue)
}
}
}

当model被修改时,对应的watcher会被推入Update queue, 与此同时还会在异步队列中添加一个task用于flush当前的Update queue。

这样一来,当前的task中的其他watcher会被推进同一个Update queue中。当前task执行结束后,异步队列下一个task执行,update queue

会被 flush,并进行后续的更新操作。

为了让 flush 动作能在当前 Task 结束后尽可能早的开始,Vue 会优先尝试将任务 micro-task 队列,具体来说,在浏览器环境中 Vue 会优

先尝试使用 MutationObserver API 或 Promise,如果两者都不可用,则 fallback 到 setTimeout。

对比两个框架可以发现 React 基于 Transition 实现的 Batch Query 是一个不依赖语言特性的通用模式,因此有更稳定可控的表现,但缺点

是无法完全覆盖所有情况,例如对于如下代码:

1
2
3
4
5
6
7
componentDidMount () {
setTimeout(_ => {
this.setState({ foo: 1 })
this.setState({ foo: 2 })
this.setState({ foo: 3 })
}, 0)
}

由于 setTimeout 的回调函数「不受 React 控制」,其中的 setState 就无法得到优化,最终会导致 render 函数执行三次。

而 Vue 的实现则对语言特性乃至运行环境有很强的依赖,但可以更好的覆盖各种情况:只要是在同一个 task 中的修改都可以进行 Batch Update 优化。





总结一下:


React 在这里的更新和事务机制使用比较通用的处理方式。


比如默认第一次应用初始化的时候是一次事务的进行,在用户交互的时候是一次新的事务开始,会在同一次同步事务中标记 batchUpdate=true,这样的做法是不破坏使用者的代码。
然后如果是 Ajax,setTimeout 等要离开主线程进行异步操作的时候会脱离当前 UI 的事务,这时候再进入此次处理的时候 batchUpdate=false,所以才会 setState 几次就 render 几次。


Vue 的策略虽然在机制上雷同,但是从根本上来讲是一种延迟的批量更新机制。


Angular 在这里也处理得很巧妙,利用 zone.js 对 task 进行拦截,对 JS 现有场景进行 AOP,这样就成功的桥接了代码。


React 的事务是纯粹的 IO 模型的适配。


那么Batch Update介绍到这里 ,在下一篇我们将参考React源码来分析setState的实现过程。


参考文档:
饿了么前端团队对于batch Update的理解: https://zhuanlan.zhihu.com/p/28532725

react 组件之间的传值

发表于 2017-08-26 | 阅读次数:

组件之间的传值,包括父子组件传值, 兄弟组件之间的传值,其中父子组件包括父组件向子组件传值和子组件向父组件传值,现在一一来介绍一下.

一.父组件向子组件传值
父组件通过state向子组件传值,

// 父组件

constructor(props){

super(props);

this.state = { name : 'yuxi' }}

render(){ return 我是子组件 }

// 子组件

constructor(props){

super(props)

}

render(){

return

{this.props.name}

}

二.子组件向父组件传值
有三种方式实现子组件向父组件传值.

(一)父组件通过state传值给子组件,子组件通过props获取父组件的传递值

//(一)传值,即在父组件中声明好自己的state,然后传值,如下//1.初始值constructor() {

super();

this.state = {

  stateValue:true    }

}//2.如有改变设定值this.setState({

stateValue: false})//3.在父组件中传值

const huoquValue = this.props.stateValue;

if ( !this.props.stateValue ) {

    console.log('stateValue', this,.props.stateValue)

}

}

值得注意一点的是,setState 是一个异步方法,需要render值行的时候才会触发。可以参考博客【 React的setState立即执行方案】。

(二)父组件将方法传给子组件,子组件通过props来获取。

//父组件文件中:

classTestHtmlextendsReact.Component{

//1.方法的声明

propsFunction() {

    console.log('声明在父组件的方法')

}

render() {

    return (

        //2.传递           

    )

}

}functionmapStateToProps(state, ownProps){ return {

}

}functionmapDispatchToProps(dispatch){ return {

    ...bindActionCreators(action, dispatch)

}

}

export default connect(mapStateToProps, mapDispatchToProps)(TestHtml)

//子组件中获取

react的状态提升.md

发表于 2017-08-23 | 阅读次数:

1.概念理解
在react中是单向数据流的设计, 即 只有父组件可以传递数据给子组件,而没有子组件传递数据给父组件的概念. 以正确的技术说明,是 拥有者组件 可以设置 被拥有者组件 中的资料,也就是主人与仆人的关系。

那么子组件要传递数据给父组件该如何沟通呢?

换句话说就是, react 如何将子组件的值暴露让父组件获取到?

可以采用一种迂回的方法, 在父组件中设置一个方法(函数), 将其通过props传递给子组件, 然后在子组件中更新state的状态,并调用父组件中传过来的方法, 将state数据作为参数传递给父组件. 这样, 改变父组件的状态,从而改变受父组件控制的所有子组件的状态. 这就是状态提升的概念. 用官方的原话就是: ‘共享 state(状态) 是通过将其移动到需要它的组件的最接近的共同祖先组件来实现的。 这被称为“状态提升(Lifting State Up)’。

官方参考网址: https://www.css88.com/react/docs/lifting-state-up.html

2.举例说明


下面举个例子说明:

App.js文件

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
import  React, { Component } from 'react'

import Item from './Item'

export default class App extends Component {

constructor(props) {

super(props)

this.state = {

options: [

{name:'(1)免费行李', value: 1 },

{name:'2', value: 2 },

{name:'3', value: 3 } ],

price: 0

}

}

changePrice = (value) => {

let price = 800

if(value === 1) price = 0

else

price *= value - 1

this.setState({price: price}) }

render() {

return (

{this.state.price}

) }}

Item.js文件

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
import React, { Component } from 'react'

export default class Item extends Component {

constructor(props) {

super(props)

this.handleChange = this.handleChange.bind(this)

}

handleChange(e){

this.props.changePrice(e.target.value)

}

render() {

var options = []

var optionArray = this.props.optionArray

options = optionArray.map(function(item, index){

return ( {item.name} )

})

return ( {options} )

}

}

说明:

2.1. 先绑定(bind)住render有用到的方法

在父组件与子组件各有用到一个自己的方法changePrice,并在render中作赋值,在React中需要bind过才会把this对住,因为在render的return语句中使用,它在重渲染(re-render)时会再次建立新的方法(函数)内容值,这样会有效能上的影响,所以要先作绑定的事,然后再render的return里面用。关于bind(this)的一些理解,在我的另一篇文章,可以参考:https://www.jianshu.com/p/f7f2636d16a9

先绑定要在类的contructor里作,像下面这样,我这写一个父组件而已,子组件一样:

constructor(props) {super(props)this.state = {price:0}//先bind类中方法this.changePrice =this.changePrice.bind(this) }

之后在render的return要改成这样:

1
2
3
4
5
render() {
return(
<div>{this.state.price}</div>
);
}

2.2 校正state(状态)里的资料,以及提升到父组件去

在子组件中的state(状态)中的资料是不是有那么必要放在子组件中,如果你还有第二个子组件、第三、第四…,它们都要用例如这里的选中资料,你放在这个子组件是违反了上面说的应用领域全局资料思维的。

先看一下子组件目前的state,是长这个样子:

this.state = {names: ['(1)免费行李','2','3'],values: ['1','2','3'],selectName:'',prices:'0'}

这里要先校正一下,names与values是代表选项中的名与值,它们是有关联的,所以应该是这样的下面结构才是好些的,value如果是要用来代表数字,就用数字就行不需要用字串:

1
options: [  {name:'(1)免费行李',value:1},  {name:'2',value:2},  {name:'3',value:3}]

选中了哪个选项这个状态资料,还是要先放在子组件中,因为子组件中有选项盒,与触发的更动方法,但选项的资料可以移到上层的父组件中:

这是上层App.js中的状态:

1
2
3
4
5
6
7
this.state = {
options: [
{name:'(1)免费行李',value:1},
{name:'2',value:2},
{name:'3',value:3}],
price:0
}

父组件也改用把state里面的选项值,用props值给子组件,所以在render里语句改成下面这样:

1
2
3
4
5
render() {
return(
<div>{this.state.price}</div>
);
}

子组件中这时可以用this.props.optionArray接到传入的选项值,所以在render方法中,改用这个来代替之前的this.state.names与this.state.values,简单改写如下:

varoptions = []varprices =this.state.pricesvaroptionArray =this.props.optionArrayfor(vari =0; i< optionArray.length; i++) { options.push({optionArray[i].name}) }

注: 这里不用for…in语句而用for语句,是因为for…in语句是个不建议用在数组资料的语法,它并不会保证取到数组成员里的顺序。for…in只会用在对象的寻遍中。

更精简的写法是用Array.map,如下:

1
2
3
4
var options = []
var prices =this.state.prices
var optionArray =this.props.optionArray
options = optionArray.map(function(item, index){return({item.name})})

接着,如果依选项选中然后计算价格这件事,规划中应该是整个应用来作的,例如有可能还有其他的组件中也有其他的选项,最后统一要来算价格,所以计算价格这件事,也应该放到父组件去,所以如同上面的改写一样,把子组件的prices状态与相关计算的代码,都提到父组件,这个子组件纯用来当选项盒用而已。子组件此时连state都可以不用有。

因为整个改写过的代码会多些,所以我把父组件与子组件中的代码整个贴上。

父组件App.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React, { Component } from 'react';
import Item from'./Item'
export default class App extends Component{
constructor(props) {
super(props)
this.state = {options: [ {name:'(1)免费行李',value:1}, {name:'2',value:2}, {name:'3',value:3} ],price:0}
this.changePrice =this.changePrice.bind(this)
}
changePrice(value){
var price =800;
if(value ===1)
price =0
else
price = (value -1) * price
this.setState({price: price}) }
render() {
return(
<div>{this.state.price}</div>
)
}}

子组件Item.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, { Component }from'react'
export default class Item extends Component{
constructor(props) {
super(props)
}
handleChange(e){
this.props.changePrice(e.target.value)
}
render() {
var options = []
var optionArray =this.props.optionArray
options = optionArray.map(function(item, index){return({item.name}) })return(

<div>{options}</div>

) }}

2.3. 目前最终进化版本

这个版本有几个改进如下,供参考:

用let/const取代var。

不用分号(;)作为语句结尾。

Item子组件改用函数定义方式,取代原先的组件定义方式。

能合并的语句都合并。

函数全用箭头函数(注意需额外加装babel-plugin-transform-class-properties)。

App.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { Component }from'react'
import Item from'./Item2'
export default class App extends Component{
constructor(props) {
super(props)
this.state = {options: [
{name:'(1)免费行李',value:1},
{name:'2',value:2},
{name:'3',value:3}],
changePrice =(value) =>{
let price =800
if(value ===1)
price =0
else
price *= value -1
render() {
return(

<div>{this.state.price}</div>

)
}}

Item.js

1
2
3
4
5
6
7
8
9
import React from'react'
const Item =(props) =>{
const optionArray = props.optionArray
const options = optionArray.map((item, index) =>{return({item.name}) })return(

{props.changePrice(e.target.value)}}> {options}

)}
export default Item



3.参考文档


参考文档: 1. https://www.cnblogs.com/zhangbob/p/6962138.html?utm_source=itdadao&utm_medium=referral

  1. https://www.css88.com/react/docs/lifting-state-up.html官方文档

3.https://blog.csdn.net/YQXLLWY/article/details/73481063

js 将图片资源转码成base64的各种场景及其实现

发表于 2017-07-15 | 阅读次数:

前言:
在项目中,经常会遇到要转码转成base64的场景, 那么什么是base64呢? 为什么要转成base64编码呢?

在阮一峰大神的JavaScript文档中是这么说的:

有时,文本里面包含一些不可打印的符号,比如 ASCII 码0到31的符号都无法打印出来,这时可以使用 Base64
编码,将它们转成可以打印的字符。
另一个场景是,有时需要以文本格式传递二进制数据,那么也可以使用 Base64 编码。


所谓 Base64 就是一种编码方法,可以将任意值转成0~9、A~Z、a-z、+和/这64个字符组成的可打印字符。
使用它的主要目的,不是为了加密,而是为了不出现特殊字符,简化程序的处理。



但这篇文章的重点不是介绍base64, 下面主要还是说说图片资源在什么情况下要使用到base64转码吧


场景一:将用户本地上传的资源转化,即用户通过浏览器点击文件上传时,将图片资源转化成base64:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var reader = new FileReader();
var AllowImgFileSize = 2100000; //上传图片最大值(单位字节)( 2 M = 2097152 B )超过2M上传失败
var file = $("#image")[0].files[0];
var imgUrlBase64;
if (file) {
//将文件以Data URL形式读入页面
imgUrlBase64 = reader.readAsDataURL(file);
reader.onload = function (e) {
//var ImgFileSize = reader.result.substring(reader.result.indexOf(",") + 1).length;//截取base64码部分(可选可不选,需要与后台沟通)
if (AllowImgFileSize != 0 && AllowImgFileSize < reader.result.length) {
alert( '上传失败,请上传不大于2M的图片!');
return;
}else{
//执行上传操作
alert(reader.result);
}
}
}

场景二:将本项目中的图片资源转化成base64,(我还没有用到过此场景,感觉场景二也可以通过场景三来实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function(){
var url = "static/img/js1.jpg";//这是站内的一张图片资源,采用的相对路径
convertImgToBase64(url, function(base64Img){
//转化后的base64
alert(base64Img);
});
}

//实现将项目的图片转化成base64
function convertImgToBase64(url, callback, outputFormat){
var canvas = document.createElement('CANVAS'),
  ctx = canvas.getContext('2d'),
  img = new Image;
  img.crossOrigin = 'Anonymous';
  img.onload = function(){
  canvas.height = img.height;
  canvas.width = img.width;
  ctx.drawImage(img,0,0);
  var dataURL = canvas.toDataURL(outputFormat || 'image/png');
  callback.call(this, dataURL);
  canvas = null;
};
  img.src = url;
}

场景三:将网络图片资源转化为base64,(感觉场景二中的资源换成绝对路径即可使用在场景三中)

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
function(){
   //这是网上的一张图片链接
   var url="https://p1.pstatp.com/large/435d000085555bd8de10";
getBase64(url)
.then(function(base64){
console.log(base64);//处理成功打印在控制台
},function(err){
console.log(err);//打印异常信息
});
}

//传入图片路径,返回base64
function getBase64(img){
function getBase64Image(img,width,height) {//width、height调用时传入具体像素值,控制大小 ,不传则默认图像大小
var canvas = document.createElement("canvas");
canvas.width = width ? width : img.width;
canvas.height = height ? height : img.height;

var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
var dataURL = canvas.toDataURL();
return dataURL;
}
var image = new Image();
image.crossOrigin = '';
image.src = img;
var deferred=$.Deferred();
if(img){
image.onload =function (){
deferred.resolve(getBase64Image(image));//将base64传给done上传处理
}
return deferred.promise();//问题要让onload完成后再return sessionStorage['imgTest']
}
}

至此,便将图片base64转码的三种场景介绍完毕了,下面是基于以上的一下拓展:

扩展:
拓展一:后台需要以纯字符串的形式上传(即去掉data:image/png;base64,截取字符串即可)

reader.result.substring(reader.result.indexOf(“,”) + 1)
拓展二:判断base64资源大小,超过2M不让上传

var AllowImgFileSize = 2100000;    //上传图片最大值(单位字节)( 2 M = 2097152 B )
if (AllowImgFileSize != 0 && AllowImgFileSize < reader.result.length) {
  alert( '上传失败,请上传不大于2M的图片!');
  return;
}

其中reader.result即是base64转码后的结果。

附上一篇 参考文档: https://blog.csdn.net/tww316/article/details/44343075


JavaScript 图片上传后base64转码直接显示

1
2
3
4
5
6
7
8
9
10
11
12
13
// 获取文件流
var fileObj = document.getElementById('inputId').files;
// 实例化一个FileReader对象
var reader = new FileReader();
// 创建一个img
var img = $('<img />');
reader.onload = function (e) {
img.attr('src', e.target.result);
$('div-selector').append(img);
}

// base64转码
reader.readAsDataURL(fileObj);

为什么要将script脚本放在body底部

发表于 2017-07-08 | 阅读次数:

说明:

本文提到的浏览器均是指Chrome。
“script标签“指的都是普通的不带其他属性的外联javascript。
web性能优化的手段并不是非黑即白的,有些手段过头了反而降低性能,所以在讨论条件和结论的时候,虽然很多条件本身会带来其他细微的负面或正面影响,为了不使论述失去重点,不会扩展太开。
一、从一个面试题说起
面试前端的时候我喜欢问一些看上去是常识的问题。比如:为什么大家普遍把这样的代码放在body最底部?(为了沟通效率,我会提前和对方约定所有的讨论都以chrome为例)

应聘者一般会回答:因为浏览器生成Dom树的时候是一行一行读HTML代码的,script标签放在最后面就不会影响前面的页面的渲染。

我很鸡贼地接着问:既然Dom树完全生成好后页面才能渲染出来,浏览器又必须读完全部HTML才能生成完整的Dom树,script标签不放在body底部是不是也一样?

留
一
段
空
白
让
你
先
想
一
想

这其实是个开放性的问题,里面涉及的概念的界定本身就很重要。

“页面渲染出来了” 指的是什么?
严格来说,我的最后一问是有歧义的:我们需要统一一下什么叫我们经常挂在嘴边的“页面渲染出来了” —— 指的是是 “首屏显示出来了” 还是 “页面完整地加载好了”(后面统称StepC) ?
如果指的是首屏显示出来了,那么问题又来了:假设网页首屏有图片,这里的“首屏” 指的是 “显示了全部图片的首屏”(后面统称StepB) 还是 “没有图片的首屏”(后面统称StepA)。

确定清楚 “页面渲染出来了” 指的是 StepA、StepB、StepC 中的哪一个是非常关键的(虽然至今还没有一个应聘者尝试这么做过),如果 “页面渲染出来了” 指的是 StepC,那么我的最后一问的答案是肯定的——script标签不放在body底部不会拖慢页面完整地加载好的时间。

显然,我们往往更关心首屏时间,所以,如果 “页面渲染出来了” 特指“没有图片的首屏”,那我的最后一问变成了下面这样,又该如何回答呢?

既然Dom树完全生成好后才能显示“没有图片的首屏”,浏览器又必须读完全部HTML才能生成完整的Dom树,script标签不放在body底部是不是也一样?

陷阱
然而上面的问题还是存在一个陷阱——既然Dom树完全生成好后才能显示“没有图片的首屏”这句话是带欺骗性的,“没有图片的首屏”并不以“完整的Dom树”为必要条件。也就是说:在生成Dom树的过程中只要某些条件具备了,“没有图片的首屏”就能显示出来。

所以,抛开这些歧义和陷阱,我的问题变成了:

script标签的位置会影响首屏时间么?

然而答案并不是那么显而易见,这得从浏览器的渲染机制说起。(再一次说明:本文所说的浏览器都是指chrome)

二、浏览器的渲染机制
Google Web Fundamentals 是一个非常优秀的文档,里面讲到了跟web、浏览器、前端的方方面面。我总结一下其中的 Ilya Grigorik 写的 Critical rendering path 浏览器渲染机制部分的内容如下:

几个概念

1
2
3
4
5
6
7
8
9
10
11
1、DOM:Document Object Model,浏览器将HTML解析成树形的数据结构,简称DOM。

2、CSSOM:CSS Object Model,浏览器将CSS代码解析成树形的数据结构。

3、DOM 和 CSSOM 都是以 Bytes → characters → tokens → nodes → object model. 这样的方式生成最终的数据。如下图所示:

bVsaO

DOM 树的构建过程是一个深度遍历过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。

4、Render Tree:DOM 和 CSSOM 合并后生成 Render Tree,如下图:

bVsaP

Render Tree 和DOM一样,以多叉树的形式保存了每个节点的css属性、节点本身属性、以及节点的孩子节点。

注意:display:none 的节点不会被加入 Render Tree,而 visibility: hidden 则会,所以,如果某个节点最开始是不显示的,设为 display:none 是更优的。(具体可以看这里)

浏览器的渲染过程
Create/Update DOM And request css/image/js:浏览器请求到HTML代码后,在生成DOM的最开始阶段(应该是 Bytes → characters 后),并行发起css、图片、js的请求,无论他们是否在HEAD里。

注意:发起 js 文件的下载 request 并不需要 DOM 处理到那个 script
节点,比如:简单的正则匹配就能做到这一点,虽然实际上并不一定是通过正则:)。这是很多人在理解渲染机制的时候存在的误区。

Create/Update Render CSSOM:CSS文件下载完成,开始构建CSSOM
Create/Update Render Tree:所有CSS文件下载完成,CSSOM构建结束后,和 DOM 一起生成 Render Tree。
Layout:有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系。
下一步操作称之为Layout,顾名思义就是计算出每个节点在屏幕中的位置。
Painting:Layout后,浏览器已经知道了哪些节点要显示(which nodes are visible)、每个节点的CSS属性是什么(their computed styles)、每个节点在屏幕中的位置是哪里(geometry)。就进入了最后一步:Painting,按照算出来的规则,通过显卡,把内容画到屏幕上。
以上五个步骤前3个步骤之所有使用 “Create/Update” 是因为DOM、CSSOM、Render Tree都可能在第一次Painting后又被更新多次,比如JS修改了DOM或者CSS属性。

Layout 和 Painting 也会被重复执行,除了DOM、CSSOM更新的原因外,图片下载完成后也需要调用Layout 和 Painting来更新网页。

看 Timeline,一目了然
我扒了一段有赞PC首页的代码到本地,通过Node跑起来。Node作为Server端,对/js/jquery.js 做了延时2s返回的处理,并且把 放到导航栏的下面,结果是这样的:

bVsaO

bVsaO

bVsaO

bVsaO

从上面的Timeline我们可以看出:

首屏时间和DomContentLoad事件没有必然的先后关系
所有CSS尽早加载是减少首屏时间的最关键
js的下载和执行会阻塞Dom树的构建(严谨地说是中断了Dom树的更新),所以script标签放在首屏范围内的HTML代码段里会截断首屏的内容。
script标签放在body底部,做与不做async或者defer处理,都不会影响首屏时间,但影响DomContentLoad和load的时间,进而影响依赖他们的代码的执行的开始时间。
三、问题的答案
回到前面的问题:

1
script标签的位置会影响首屏时间么?

答案是:不影响(如果这里里的首屏指的是页面从白板变成网页画面——也就是第一次Painting),但有可能截断首屏的内容,使其只显示上面一部分。

为什么说是“有可能”呢?,如果该js下载地比css还快,或者script标签不在第一屏的html里,实际上是不影响的。明白这一影响边界非常重要,这样我们在考察页面性能瓶颈的时候就有的放矢了。举个例子:在网页的第二屏有一个通用模块,实际上我们是可以把它的js逻辑独立成一个文件,将模块的html和js标签放在一起做成独立的模板引进来的(如果它的js比较小或者说因为多了一个文件会多占用一个TCP连接和带宽,这实际上是另外一个话题了,请参考我文章开头的声明)。

四、总结、再进一步
所以,总算弄清楚这个众所周知的常识了。我们来总结一下:

如果script标签的位置不在首屏范围内,不影响首屏时间 所有的script标签应该放在body底部是很有道理的
但从性能最优的角度考虑,即使在body底部的script标签也会拖慢首屏出来的速度,
因为浏览器在最一开始就会请求它对应的js文件,而这,占用了有限的TCP链接数、带宽甚至运行它所需要的CPU。
这也是为什么script标签会有async或defer属性的原因之一。

可是,在复杂的实际应用场景中,要贯彻这几条结论可能会遇到问题,比如:

你的页面是分模块来写的,每一个模块都有自己的html、js甚至css,当把这些模块凑到一个页面中的时候就会出现js自然而然地出现在HTML中间部分。你很难把script标签都放到底部
即使你把script标签都放到底部,但script标签的存在终究是拖慢了首屏时间、DomContendLoad和loaded的时间。如果只有一个script标签,我们可以加一个async,但多个async的script标签的结果会是js文件被乱序执行的,这显然不是我们想要的。
我们也遇到了这样的问题,所以就做了一个开源项目:Tiny-Loader —— A small loader that load CSS/JS in best way for page performance 简单好用。

来自:https://blog.csdn.net/garvisjack/article/details/71077986#t1

相关文章
聊聊前端排序的那些事 601Views

浏览器的布局绘制与DOM操作 214Views
小技巧:一行代码让浏览器瞬间变成临时编辑器 761Views
浅析渲染引擎与前端优化 245Views
JS放在head和放在body中的区别 286Views

记录博客搭建踩过的坑

发表于 2017-03-25 | 分类于 博客搭建 | 阅读次数:

本人的博客使用的是hexo+github搭建的一套独立博客, 主题采用的是next,经过两天的折腾, 博客也弄得有模有样了.(微笑).
现在记录一下踩过的坑,希望自己可以好好总结, 能帮助到萌新就更好了,哈哈!
问题不分顺序,本人比较懒, 想到啥就写啥.

1.解决编辑器的问题
hexo是使用markdown编辑器的,如果不想在本地安装markDown,(我就懒得安装), 那么CSDN了解一下.
在CSDN写文章的编辑器中 , 切换成markDown, 编辑完之后选择导出到本地, 然后将这个文件放置到你的相应文件夹下, 搞定 .

2.给hexo博客添加系列文章功能
之前已经讲过, hexo new page categories 便可以新建一个categories页面,

1
2
3
4
5
---
title: categories
date: 2018-03-02 12:33:16
type: "categories"
---

实际上调用hexo new page xxx后,会在/source/categories/目录下生成一个index.md文档,在此文档头部加上上面这段即可。实际上,index.md里只需要有这个声明即可,其他内容并不会显示出来,写了也没用。
(1)新建文章
hexo new ‘文章名’
(2)设置分类
这一步是重点了, 在上一步中,我们可以看到已经创建了一个 ‘文章名.md’的文件,在source/_posts文件夹下可以找到.
大概长这样:

1
2
3
4
5
---
title: 记录博客搭建踩过的坑
date: 2017-03-25 14:47:58
tags:
---

你只需要在tags标签下添加分类名称就可以了,像这样:

1
2
tags:
categories: 博客搭建

PS. 无论是page,还是post的文章,都是以.md格式结尾,在hexo g的过程中会产生对应的.html文档,然后hexo d到Github上的也只是html文档,不是.md格式的文档。
另外就是,需要注意一点:如果有启用多说 或者 Disqus 评论,默认页面也会带有评论。需要关闭的话,请添加字段 comments 并将值设置为 false,如:

1
2
3
4
5
6
---
title: categories
date: 2018-03-02 12:33:16
type: "categories"
comments: false
---

3.HEXO 出现 > -bash: hexo: command not found

在执行hexo deploy的时候一直出现ERROR Deployer not found: git。
_config.yml是配置文件,见下图:hexo在2点几的版本中type: github。之后的版本是type: git
这里写图片描述
解决方法:

npm install hexo-deployer-git –save
hexo d -g

4.将博客部署到github pages出问题

最常见的就是两个问题, (1)ssh Key的问题, (2) github上setting的问题
如何部署就不说了, 这里贴上两篇教程 ,https://blog.csdn.net/u011974987/article/details/51331822/
https://www.cnblogs.com/imapla/p/5533000.html

(1)ssh Key的问题
这里写图片描述
照着这篇教程, 重新生成一个sshKey, 并将公钥放置到你的仓库中.

(2)setting的问题

这里需要说明的是, github需要设置pages的仓库名称, 必须是以io结尾的., 这个命名也是个坑, 格式必须是youname.github.io这种。其次youname必须是你的github的用户名.
在仓库的setting的options中,找到GitHub Pages模块,在source中选择分支,并点击保存,再刷新页面就会看到有一个网址, 类似这样:
这里写图片描述

如果是需要在README.md中查看到博客网址的话,可以在README.md中使用一个a标签,href链接地址就是这个红色框的地址. 如果是其他仓库,则需要加上index.html索引文件 .
如果没有出现上面所说的网址,打开链接的错误地址,如果是遇到下面这个问题 :
这里写图片描述
很明显,是部署失败了, 那么就重新部署吧.
部署之前一定要安装这个包

1
npm install hexo-deployer-git –save

然后执行 hexo d -g

5.空格问题
需要注意的是,在_config.yml配置文件中,属性名和属性值之间一定要有一个空格
不然你会遇到类似下面这种问题:

1
错误提示:FATAL bad indentation of a mapping entry at line 72, column 7:

或者这种问题

1
2
3
4
5
错误提示:
You should configure deployment settings in _config.yml first!
Available deployer plugins:
git
For more help, you can check the online docs: https://hexo.io/

6.404可能原因
(1).域名解析错误。

检查: windows下cmd命令符输入ping cheatlys.info(你的域名) 看一下ip地址,在ping一下你github上的,ping liuyongshun.github.io看一下是否一样,并且没有请求超时。

(2).你的域名是通过国内注册商注册的,因没有实名制而无法访问。

(3).浏览器缓存,路由器缓存。可尝试清除浏览器缓存再访问或者换个浏览器访问,或者换个局域网访问。

(4). 你的hexo配置有问题,而导致index页面在主域名的下一级目录。找到index页面,在域名后面添加下一级目录。看是否能访问index页面(此时样式可能是乱的)。直接在.github.io的库(相当于根目录,在_config.yml配置root时直接是/如果这些内容在blog下,root应该是/blog/)下边展开这些内容。
这里写图片描述

7.绑定域名
你需要在本地的source下建立CNAME文件(是文件不是文件夹也没有拓展名),内容就是你买的的域名(例:不要www.cheatlys.info,而是写cheatlys.info前者直接输入cheatlys.info不会连接到你的网站。)

暂时先写到这里啦,后面想起来再写,

补充日常博客管理
日常修改
在本地对博客进行修改(添加新博文、修改样式等等)后,通过下面的流程进行管理:

依次执行git add .、git commit -m “…”、git push origin hexo指令将改动推送到GitHub(此时当前分支应为hexo);
然后才执行hexo generate -d发布网站到master分支上。
虽然两个过程顺序调转一般不会有问题,不过逻辑上这样的顺序是绝对没问题的(例如突然死机要重装了,悲催….的情况,调转顺序就有问题了)。

本地资料丢失
当重装电脑之后,或者想在其他电脑上修改博客,可以使用下列步骤:

使用git clone git@github.com:CrazyMilk/CrazyMilk.github.io.git拷贝仓库(默认分支为hexo);
在本地新拷贝的CrazyMilk.github.io文件夹下通过Git bash依次执行下列指令:npm install hexo、npm install、npm install hexo-deployer-git(记得,不需要hexo init这条指令)。

12
大鱼吃小鱼@

大鱼吃小鱼@

记录IT,记录成长,愿在输得起的年纪,创造更多的奇迹

12 日志
3 分类
2 标签
GitHub Zhihu Weibo juejin
友情连接
    学习资源收集 开发指南指引 博客搭建指导
© 2017 — 2018 大鱼吃小鱼@
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4