ycshill / shared Goto Github PK
View Code? Open in Web Editor NEW技术调研,尝试,demo,书籍,想法的分享
技术调研,尝试,demo,书籍,想法的分享
npm install --global create-react-app
create-react-app frist_react_app
即使现在,我们还是要说在HTML中直接使用onclick很不专业,原因如下:
□ onclick添加的事件处理函数是在全局环境下执行的,这污染了全局环境,很容易产生意料不到的后果;
□ 给很多DOM元素添加onclick事件,可能会影响网页的性能,毕竟,网页需要的事件处理函数越多,性能就会越低;
□ 对于使用onclick的DOM元素,如果要动态地从DOM树中删掉的话,需要把对应的时间处理器注销,假如忘了注销,就可能造成内存泄露,这样的bug很难被发现。
上面说的这些问题,在JSX中都不存在。
首先,onClick挂载的每个函数,都可以控制在组件范围内,不会污染全局空间。
我们在JSX中看到一个组件使用了onClick,但并没有产生直接使用onclick(注意是onclick不是onClick)的HTML,而是使用了事件委托(event delegation)的方式处理点击事件,无论有多少个onClick出现,其实最后都只在DOM树上添加了一个事件处理函数,挂在最顶层的DOM节点上。所有的点击事件都被这个事件处理函数捕获,然后根据具体组件分配给特定函数,使用事件委托的性能当然要比为每个onClick都挂载一个事件处理函数要高。
因为React控制了组件的生命周期,在unmount的时候自然能够清除相关的所有事件处理函数,内存泄露也不再是一个问题。
一个组件只做一件事情
prop
开发者在代码中定义propTypes,在开发过程中避免犯错,但是在发布产品代码时,用一种自动的方式将propTypes去掉,这样最终部署到产品环境的代码就会更优。现有的babel-react-optimize就具有这个功能,可以通过npm安装,但是应该确保只在发布产品代码时使用它
state
this.setState
,原因如下:直接修改this.state的值,虽然事实上改变了组件的内部状态,但只是野蛮地修改了state,却没有驱动组件进行重新渲染,既然组件没有重新渲染,当然不会反应this.state值的变化;而this.setState()函数所做的事情,首先是改变this.state的值,然后驱动组件经历更新过程,这样才有机会让this.state里新的值出现在界面上。
组件不应该改变prop的值,而state的存在的目的就是让组件来改变的;
组件的生命周期
分为【装载过程】、【更新过程】、【卸载过程】
装载过程
在装载过程中,componentWillMount会在调用render函数之前被调用,componentDidMount会在调用render函数之后被调用,这两个函数就像是render函数的前哨和后卫,一前一后,把render函数夹住,正好分别做render前后必要的工作。
不过,我们通常不用定义componentWillMount函数,顾名思义,componentWillMount发生在“将要装载”的时候,这个时候没有任何渲染出来的结果,即使调用this.setState修改状态,也不会引发重新绘制,一切都迟了。换句话说,所有可以在这个componentWillMount中做的事情,都可以提前到constructor中间去做,可以认为这个函数存在的主要目的就是为了和componentDidMount对称。
而componentWillMount的这个兄弟componentDidMount作用就大了。
需要注意的是,render函数被调用完之后,componentDidMount函数并不是会被立刻调用,componentDidMount被调用的时候,render函数返回的东西已经引发了渲染,组件已经被“装载”到了DOM树上。
我们还是以ControlPanel为例,在ControlPanel中有三个Counter组件,我们稍微修改Counter的代码,让装载过程中所有生命周期函数都用console.log输出函数名和caption的值,比如,componentWillMount函数的内容如下:
componentWillMount() { console.log('enter componentWillMount ' + this.props.caption); }
上面修改并没有添加任何功能,只是通过console.log输出一些内容,然后我们刷新网页,在浏览器的console里我们能够看见:
enter constructor: First enter componentWillMount First enter render: First enter constructor: Second enter componentWillMount Second enter render: Second enter constructor: Third enter componentWillMount Third enter render: Third enter componentDidMount First enter componentDidMount Second enter componentDidMount Third
> 可以清楚地看到,虽然componentWillMount都是紧贴着自己组件的render函数之前被调用,componentDidMount可不是紧跟着render函数被调用,当所有三个组件的render函数都被调用之后,三个组件的componentDidMount才连在一起被调用。
之所以会有上面的现象,是因为render函数本身并不往DOM树上渲染或者装载内容,它只是返回一个JSX表示的对象,然后由React库来根据返回对象决定如何渲染。而React库肯定是要把所有组件返回的结果综合起来,才能知道该如何产生对应的DOM修改。所以,只有React库调用三个Counter组件的render函数之后,才有可能完成装载,这时候才会依次调用各个组件的componentDidMount函数作为装载过程的收尾。
componentWillMount和componentDidMount这对兄弟函数还有一个区别,就是componentWillMount可以在服务器端被调用,也可以在浏览器端被调用;而componentDidMount只能在浏览器端被调用,在服务器端使用React的时候不会被调用。
到目前为止,我们构造的React应用例子都只在浏览器端使用React,所以看不出区别,但后面第12章关于“同构”应用的介绍时,我们会探讨在服务器端使用React的情况。
至于为什么只有componentDidMount仅在浏览器端执行,这是一个实现上的决定,而不是设计时刻意为之。不过,如果非要有个解释的话,可以这么说,既然“装载”是一个创建组件并放到DOM树上的过程,那么,真正的“装载”是不可能在服务器端完成的,因为服务器端渲染并不会产生DOM树,通过React组件产生的只是一个纯粹的字符串而已。
不管怎样,componentDidMount只在浏览器端执行,倒是给了我们开发者一个很好的位置去做只有浏览器端才做的逻辑,比如通过AJAX获取数据来填充组件的内容。
在componentDidMount被调用的时候,组件已经被装载到DOM树上了,可以放心获取渲染出来的任何DOM。
在实际开发过程中,可能会需要让React和其他UI库配合使用,比如,因为项目前期已经用jQuery开发了很多功能,需要继续使用这些基于jQuery的代码,有时候其他的UI库做某些功能比React更合适,比如d3.js已经支持了丰富的绘制图表的功能,在这些情况下,我们不得不考虑如何让React和其他UI库和平共处。
以和jQuery配合为例,我们知道,React是用来取代jQuery的,但如果真的要让React和jQuery配合,就需要是利用componentDidMount函数,当componentDidMount被执行时,React组件对应的DOM已经存在,所有的事件处理函数也已经设置好,这时候就可以调用jQuery的代码,让jQuery代码在已经绘制的DOM基础上增强新的功能。
在componentDidMount中调用jQuery代码只处理了装载过程,要和jQuery完全结合,又要考虑React的更新过程,就需要使用下面要讲的componentDidUpdate函数。
- 更新过程
- 执行顺序:
- componentWillReceiveProps
- shouldComponentUpdate
- render
- componentDidUpdate
- componentWillReceiveProps
只要父组件的render函数被调用,在render函数里面被渲染的子组件就会经历更新的过程,不管父组件传给子组件的props有没有改变;
Redux
redux的三个基本原则: 唯一数据源;保持状态只读;数据改变只能通过纯函数完成;
唯一数据源:
只有一个Store,如何设计Store上的状态结构是要考虑的重点,原则是避免冗余的数据;
保持状态只读:
修改状态,只能够通过action修改,但是这个改变不是去修改状态上的值,而是创建一个新的状态对象返回给Redux。
数据改变只能通过纯函数
reducer(state, action)
例如:
reducer(state, action) => {
switch(action.type) {
case ActionTypes.type:
return {...state, count: count + 1}
}
}
说明,Redux的reducer只负责计算状态,不负责存储状态;
聪明组件和傻瓜组件
Context ??? (后期有时间了解一下)
Context 这个功能提供了一个全局可以访问的对象,一般避免使用
React-Redux
做了两个事情: 提供一个connect;提供一个Provider
简单的说,一个高级组件就是一个函数,这个函数接受一个组件作为输入,然后返回一个新的组件作为结果,而且返回新的组件具有新的组件不具有的功能;
fetch: 用fetch来访问服务器数据资源,fetch函数返回的结果是Promise对象,虽然被fetch广为接受,大有取代其他网络访问方式的架势,但是它有一个特性一直被人诟病,那就是fetch认为只要服务器返回一个合法的HTTP响应就算成功,就会调用then提供的回调函数,即使这个HTTP响应的状态码是表示出错了的400或者500。正因为fetch的这个特点,所以我们在then中,要做的第一件事就是检查传入参数response的status字段,只有status是代表成功的200的时候才继续,否则以错误处理。
fetch(apiUrl).then((response) => {
if (response.status !== 200) {
throw new Error('Fail to get response with status ' + response.status);
}
访问服务器数据的时机和步骤
Redux 访问服务器
中间件用来增强Redux Store的dispatch方法。
中间件模板:每个中间件都是一个函数,返回一个接收next参数的函数,而接收next参数的函数又返回一个接收actions参数的函数。next参数本身也是一个函数,中间件调用这个next函数通知Redux自己的处理工作已经结束。例如以下的代码:
function doNothingMiddleware({dispatch, getState}) { //这两个参数是非必须的
return function(next) {
return function(action) {
// 这里面可以做很多事情,比如调用dispatch派发出一个action对象;tedia调用getState获取当前的Redux Store上的状态;访问action对象上action上的所有数据;
return next(action);
}
}
}
上面的代码就等同于:
({dispatch, getState}) => next => action => next(action)
中间件的使用:
以同时使用 redux-thunk 和 redux-devtools 增强功能为例:
import {createStore, applyMiddleware, compose} from 'redux';
import thunkMiddleware from 'redux-thunk';
const win = window;
const storeEnhancers = compose( //redux提供的compose函数,可以把多个store Enhancer c串联成为一个函数,因为createStore 只能够接受一个Store Enhancer 的参数;
applyMiddleware(...middleware), // **一定要作为第一个**
(win && win.devToolsExrension) ? win.devToolsExension : f=> f;
)
// createStore 接受三个参数,第一个参数是reducer,第二个如果是对象,被认为是创建Store的初始化状态,第三个参数是增强器
const store = createStore(reducer, storeEnhancers);
中间件的开发?????????
以上画 ?号的都要再读一遍!!!!
delete
可以删除 2. 是否可以改为访问其属性 3. 是否可以修改属性特性;Object.defineProperty(对象,属性名,特性组成的对象)
delete
删除;2.是否能修改特性;3.是否能把属性修改为数据属性;undefined
,如果定义的时候没有写,则不能写入;undefined
, 定义时没有,不能独缺;var person = {
name: 'lily'; // 数据属性
_weight: 50;
}
// height就是访问器属性
Object.defineProperty(person, 'height', {
get: function() {
return 168;
},
set: function(newVal) {
newVal > 180 ? this._weight = 60 : 50;
}
})
三个方法
Object.defineProperty(对象,属性名, 特性组成对象)
可以 1. 修改数据属性特性; 2.可以定义访问器属性;Object.defineProperties(对象,属性的字面量对象)
可以同一时间,创建多个属性; Object.defineProperties(person, {
name: {
writable: true,
value: 'lily',
},
height: {
get: function() { return 168 }
}
})
Object.getOwnPropertyDescriptor(对象,属性名)
标签(空格分隔): HTTP
本篇博客是读完《图解HTTP》之后,摘录和总结其中个人感觉需要知道的知识点,因为这部分知识点比较枯燥乏味,所以本篇博客力求简单,明了,有趣。
- WEB 及其网络的基础
- 确保Web安全的HTTPS
- 确认访问用户身份的认证
- 基于HTTP追加协议
ISO制定的OSI参考模型的过于庞大、复杂招致了许多批评。与此对照,由技术人员自己开发的TCP/IP协议栈获得了更为广泛的应用。
####IP协议(网路层)
####TCP 协议(传输层)
TCP 协议是提供可靠的字节流服务;
HTTP-超文本传输协议,是位于计算机网络中的应用层,HTTP是建立在TCP协议之上,所以HTTP协议的瓶颈及其优化技巧都是基于TCP协议本身的特性,例如tcp建立连接的3次握手和断开连接的4次挥手以及每次建立连接带来的RTT延迟时间;
HTTP 发展起来是因为在互联网的黎明时期,CERN(欧洲核子研究组织)的蒂姆.博纳斯-李 博士提出了一种让远隔两地的研究者们共享知识的设想。最初的设想的基本理念是:借助多文档之间互相关联行程超文本,连成互相参阅的WWW(万维网)。后来提出了三项技术:作为页面的文本标记语言-HTML(超文本标记语言);作为文档传输协议的-HTTP(超文本传输协议);指定文档所在地址的URL(统一资源定位符);
由于 HTTP 的以上缺点,为了确保安全就诞生了HTTPS。简单的说 ** HTTPS = HTTP + 加密 + 认证 + 完整性保护**。HTTPS并不是一种新的协议,只是HTTP通信接口部分用SSL和TLS协议代替而已。例如,HTTP直接和TCP通信,使用了SSL时候,就会演变为先和SSL通信,再由SSL和TCP通信。
https的通信流程(阮大神的一篇博文):http://www.ruanyifeng.com/blog/2011/08/what_is_a_digital_signature.html
SSL客户端认证和表单认证结合,SSL客户端认证来认证客户端的计算机,而表单认证来确定用户本人的行为;
在应用层和传输层之间增加了一个二进制分帧层,以达到“在不改动HTTP语义,HTTP方法、状态码、URI机及首部字段的情况下,突破HTTP/1.1 的性能限制,改进传输性能,实现低延迟和高吞吐量”;
在二进制分帧层上,HTTP2.0 会将所有的信息分割为更小的消息和帧,并对它们采用二进制格式的编码,其中HTTP1.X 的首部信息会被封装到Headers帧,而request body则封装到Data帧里。
HTTP2.0 通信都在一个连接上完成,这个连接可以承载任意数量的双向数据流。每个数据流以消息的形式发送,而消息又是由一个或者多个帧组成,这些帧可以乱序发送,然后再根据每个帧首部的流标识符重新组装;
HTTP/2.0规定了在客户端和服务器端会**使用并且维护「首部表」**来跟踪和存储之前发送的键值对,对于相同的头部,不必再通过请求发送,只需发送一次。
如果请求中不包含首部,那么首部的开销就是零字节,此时所有的首部都自动使用之前请求发送的首部;如果首部发变化了,那么只需要发送变化了数据在Headers帧里面,新增或者修改的首部帧会被追加到“首部表”。首部表在HTTP2.0连接存续4qi期内始终的存在,由客户端和服务器共同渐进地更新。
HTTP2.0所有的通信都是在一个TCP连接上完成的。HTTP2.0 把 HTTP 协议通信的基本单位缩小为一个个的帧,这些帧对应着逻辑流中的消息。**这些帧并行地同在一个TCP连接上双向交换消息。**也就是说同一链接上有多个不同方向的数据流在传输。客户端可以一边乱序发送streams,也可以一边接收服务器的响应,而服务器端同理。
优点
HTTP性能的关键在于低延迟,而不是高带宽。大多数的HTTP连接的时间短,而且是突发性的,但是TCP只在长时间连接传输大块数据时效率才最高。HTTP2.0 通过让所有的数据流公用一个连接,可以更有效地使用TCP连接,让高带宽也能真正服务于HTTP的性能。
同时,单链接多资源的方式,有如下好处:
可以减少服务器的压力,内训占用少,连接的吞吐量大;
由于 TCP 连接减少,使得网络阻塞的情况得以改观;
慢启动时间减少,拥塞和丢包恢复速度更快。
因此, “资源合并减少请求”的优化手段对于HTTP2.0来说是没有效果的。
既然所有资源都是并行发送,那么就需要“优先级”的概念了,这样就可以对重要的文件进行先传输,加速页面的渲染。
在 HTTP2.0中,服务器推送是指客户端请求之前发送数据的机制。
虽然 HTTP2.0 协议并没有声明一定要使用SSL,但是Google Chrome 等浏览器强制要使用 HTTP2.0 必须使用SSL。
标签(空格分隔): Flutter
null
,和js中的差不多;var a;
print(a); // null
a = 10;
print(a); // 10
a = 'Hello Dart';
print(a); // Hello Dart;
// 总结 var 和js 一样, 未初始化的时候是 null,(变量)
// 声明一个只赋值一次的变量
final b = 20;
print(b); // 20;
// 声明一个常量(编译时)
const c = 30;
print(c); // 30
int
double
注意定义了double
类型字段的时候,可以赋值为整数的,但是打印的时候还是浮点的
num a = 10;
print(a); // 10
a = 10.5;
print(a); // 10.5
int b = 10;
// b = 10.5; 不可以
print(b);
double c = 10.5;
print(c); // 10.5
c = 2;
print(c); // 2.0
数值型操作
+,-,*,/,%,~/
;
double
,就会自动转化为double
; print(a + b); // 有一个是浮点型的就是浮点型的 20.5
print(b + d); // 21
print(d - a); // 0.5
print(b * d); // 110;
print(d/a); // 1.0476190476190477
print(d ~/a); // 1 取整
print(d%a); // 0.5 取余
常用属性
int a = 2;
print(a.isNaN); // false
print(a.isEven); // true
print(a.isOdd); // false
常用方法
int a = -100;
print(a.abs());
double b = 10.5;
print(b.round()); // 四舍五入 11
print(b.floor()); // 向下取整 10
print(b.ceil()); // 向上取整 11
print(b.toInt()); // 10
print(a.toDouble());
String a = 'hello string'; // "" 使用单引号,双引号创建字符串
print(a); // hello string
String b = ''' hello
multi line string'''; // """ 使用三个单引号,双引号创建多行字符串
print(b);
// hello
// multi line string
String c = 'hello \n not raw'; // \n 会被转换为换行符
print(c);
// hello
// not raw
String d = r'hello \n raw'; // 使用r创建原始的raw字符串, 其中的\n不会被转义
print(d); // hello \n raw
*4
拼接四个一样的字符串;[2]
返回字符串第二个位置的元素;${表达式}
$a
;length
isEmpty
isNotEmpty
;// 常用的操作
String a = 'i am a girl';
print(a + 'i love life'); // i am a girli love life
print(a*3); // i am a girli am a girli am a girl
print(a[2]); // a
int b = 10;
int c = 10;
print('b is $b'); // b is 10
print('a + b = ${b + c}'); // a + b = 20
// 常用的属性
print(a.length); // 11
print(a.isEmpty); // false
print(a.isNotEmpty); // true
// 常用的方法
String d = 'this is a sun day';
print(d.contains('is')); // true;
print(d.substring(2,4)); // 掐头去尾 is
print(d.startsWith('a')); // false
print(d.endsWith('ay')); // true
print(d.indexOf('is')); // 2
print(d.lastIndexOf('is')); // 5
print(d.toLowerCase()); // this is a sun day
print(d.toUpperCase()); // THIS IS A SUN DAY
print(d.split(' ')); // [this, is, a, sun, day]
print(d.replaceAll('s', 'w')); // thiw iw a wun day
print(d.replaceFirst('s', 'w')); // thiw is a sun day
print(d.replaceRange(0, 5, 'r')); // ris a sun day
String e = ' this is a sun day ';
print(e.trim()); // this is a sun day
print(e.trimLeft());
print(e.trimRight()); // this is a sun day
bool a = true;
print(a); // true;
list和js中的数组很相似
创建List
// 创建List
List a = [1, 3, 4, true, 'hello'];
print(a); // [1, 3, 4, true, hello]
List b = const [1, 2, 4];
// b[1] = 'eh'; // Cannot modify an unmodifiable list
List c = new List();
print(c); // []
常用操作
// List常用的操作
List d = [1, true, 'hello'];
print(d); // [1, true, hello]
print(d[0]); // 1
d[0] = 4;
print(d); // [4, true, hello]
print(d.length); // 3
d.add('world');
print(d); // [4, true, hello, world]
d.insert(2, false); // 将ele插入数组的指定位置
print(d); // [4, true, false, hello, world]
d.remove('hello');
print(d); // [4, true, false, world]
d.removeLast(); // 删除最后一个
print(d); // [4, true, false]
d.removeAt(1); // 删除指定位置
print(d); // [4, false]
d.addAll([5, 6, 0]);
print(d); // [4, false, 5, 6, 0]
d.removeRange(0, 1);
print(d); // [false, 5, 6, 0] 掐头去尾
print(d.indexOf(6)); // 2
d.addAll([5, 6, 0]);
print(d); // [false, 5, 6, 0, 5, 6, 0]
print(d.lastIndexOf(0)); // 6
d.remove(false);
print(d); // [5, 6, 0, 5, 6, 0]
d.sort();
print(d); // [0, 0, 5, 5, 6, 6]
print(d.sublist(2)); // [5, 5, 6, 6]
d.shuffle();
print(d); // [6, 5, 0, 6, 5, 0]
d.forEach(print);
/**
*
flutter: 0
flutter: 5
flutter: 6
flutter: 0
flutter: 5
flutter: 6
*/
print(d.asMap()); // {0: 5, 1: 5, 2: 0, 3: 6, 4: 6, 5: 0}
键值对的形式,有点像对象
var a = {'first': 'a', 'second': 'b'};
var a = const {'first': 'a', 'second': 'b'}
;var a= new Map()
;// 创建Map
Map a = {
'name': 'lily',
'age': '26',
};
print(a); // {name: lily, age: 26}
// 创建一个不可变的map
Map b = const {
'name': 'lily',
'age': '26',
};
// b['name'] = 'xiaoming'; // Cannot set value in unmodifiable Map
print(b);
Map c = new Map();
print(c); // {}
void main() {
runApp(MaterialApp(
title: 'dart 的基础语法',
home: FirstPage(),
));
// 创建Map
Map a = {
'name': 'lily',
'age': '26',
};
print(a); // {name: lily, age: 26}
// 创建一个不可变的map
Map b = const {
'name': 'lily',
'age': '26',
};
// b['name'] = 'xiaoming'; // Cannot set value in unmodifiable Map
print(b);
Map c = new Map();
print(c); // {}
// 常用的操作
Map d = {
'name': 'lily',
'age': '26',
};
print(d['name']); // lily
d['name'] = 'xiao';
print(d); // {name: xiao, age: 26}
print(d.length); // 2
print(d.isNotEmpty); // true
print(d.isEmpty); // false
print(d.keys); // (name, age)
print(d.values); // (xiao, 26)
print(d.containsKey('name')); // true
print(d.containsValue(3)); // false
d.remove('name');
print(d); // {age: 26}
d.forEach(getKeyValue); //key-age, value-26
}
void getKeyValue(key, value) {
print('key-$key, value-$value');
}
可变数据类型;
dynamic a = 0;
print(a); // 0
a = 'hello';
print(a); // hello
List arr = new List<dynamic>();
arr.add(1);
arr.add('hello');
print(arr); // [1, hello]
int a = 10;
int b = 11;
print(a + b); // 21
print(a - b); // -1
print(a * b); // 110
print(b / a); // 1.1
print(b ~/ a); // 1
print(b % a); // 1
int a = 10;
int b = 11;
print(a == b); // false
print(a != b); // true
print(a > b); // false
print(a < b); // true
print(a >= b); // false
print(a <= b); // true
print(a++); // 10
print(++a); // 12 上面的表达式使用a的值的时候,就++啦
print(a--); // 12
print(--a); // 10
String strA = '123';
String strB = '123';
print(strA == strB); // true
bool a = true;
bool b = false;
print(!a); // false
print(a && b); // false
print(a || b); // true
b ??= a
如果b有值的话,就取b的值,如果b没值的话,就取=
后面的值;int a = 10; // =
print(a); // 10
int b;
b ??= a;
print(b); // 10; 如果b没有值,就会读取 = 后面的值
int c = 5;
c ??= a;
print(c); // 5 如果c有值,就会读取c本身的值
print(a += b); // 20
print(a -= b); // 10
print(a *= b); // 100
// print(a /= b); // value of type 'double' can't be assigned to a variable of type 'int'
print(a ~/= b); // 10
print(a %= b); // 0
double d = 11.0;
print(d /= b); // 1.1
? = :
bool isMan = false;
print(isMan ? '$isMan, he is a man' : '$isMan, he is not a man'); // false, he is not a man
isMan = true;
print(isMan ? '$isMan, he is a man' : '$isMan, he is not a man'); // true, he is a man
String strA;
String strB = 'hello';
print(strA ?? strB); // hello, 如果前面是null的话,就读取后面的
print(strB ?? strA); // hello, 如果前面不是null的话,就读取前面的值
if
if else
if... else if... else...
for
for in
语句List arr = [1, 2, 3, 4];
for(int i = 0; i < arr.length; i++){
print('$i--${arr[i]}');
/***
flutter: 0--1
flutter: 1--2
flutter: 2--3
flutter: 3--4
*/
}
for(int item in arr){
print(item);
/**
flutter: 1
flutter: 2
flutter: 3
flutter: 4
*/
}
int count = 0;
while(count < 5) {
print(count++);
}
/**
flutter: 0
flutter: 1
flutter: 2
flutter: 3
flutter: 4
*/
print('+++$count+++'); // 5
do {
print(count--);
} while (count > 0 && count < 5);
/**
*
flutter: 5
flutter: 4
flutter: 3
flutter: 2
flutter: 1
*/
break
break 跳出本次最近的循环;
continue
continue 进入跳过本次循环,进入下一次循环;
List arr = [1, 2, 3, 4];
for (int item in arr) {
if (item == 2) {
break; // 跳出按着的循环
}
print(item); // 1
}
print('----------');
for (int item in arr) {
if (item == 2) {
continue; // 跳出本次循环
}
print(item); // 1 3 4
}
print('-------');
List inner = ['a', 'b', 'c'];
for(int item in arr) {
for(String i in inner) {
print(i);
break; // 跳出临近的本次循环
/**
* flutter: a
flutter: a
flutter: a
flutter: a
*/
}
}
和其他的语句不同的是可以加入continue flag
,会跳转到flag
的地方执行语句;
String language = 'java';
// break;
switch(language) {
case 'js':
print('js');
break;
case 'java':
print('java');
break;
case 'dart':
print('dart');
break;
default:
print('none');
}
// 使用 continue
String language1 = 'dart';
switch(language1) {
Tag:
case 'js':
print('js');
break;
case 'java':
print('java');
break;
case 'dart':
print('dart');
continue Tag;
default:
print('none');
}
/**
* flutter: java
flutter: dart
flutter: js
*/
Function
;return null
可选参数要放在后面;
void main() {
runApp(MaterialApp(
title: 'dart 的基础语法',
home: FirstPage(),
));
print(getPerson('lily', grade: '一年级')); // name-lily,age-null,grade-一年级
print(getPersonInfo('lily',10,)); // 使用位置可选参数:name-lily,age-10,grade-null
}
// 使用命名可选参数
String getPerson(String name, {int age, String grade}) {
return 'name-$name,age-$age,grade-$grade';
}
// 使用位置可选参数
String getPersonInfo(String name, [int age, String grade]) {
return '使用位置可选参数:name-$name,age-$age,grade-$grade';
}
void main() {
runApp(MaterialApp(
title: 'dart 的基础语法',
home: FirstPage(),
));
print(getPerson('lily', grade: '一年级')); // name-lily,age-10,grade-一年级
}
// 使用命名可选参数
String getPerson(String name, {int age = 10, String grade = "大学生"}) {
return 'name-$name,age-$age,grade-$grade';
}
void main() {
runApp(MaterialApp(
title: 'dart 的基础语法',
home: FirstPage(),
));
Function fun = printHello; // 作为一个参数,传递给变量
fun(); // hello
}
void printHello() {
print('hello');
}
void main() {
runApp(MaterialApp(
title: 'dart 的基础语法',
home: FirstPage(),
));
Function fun = printHello; // 作为一个参数,传递给变量
fun(); // hello
List hello = ['h', 'e', 'l', 'l', 'o'];
print(ListTimes(hello, times)); // [hhh, eee, lll, lll, ooo]
}
void printHello() {
print('hello');
}
// 返回三次该字符串
String times(str) {
return str * 3;
}
// 根据List 返回一个新的List
List ListTimes(List list, String times(str)) { // 作为一个函数的参数传入
for(int i = 0; i< list.length; i++) {
list[i] = times(list[i]);
}
return list;
}
Function fun = (str) {
print('hello---$str');
};
fun('1'); // hello---1
(参数1,参数2){
方法体
return 返回值;
}
void main() {
runApp(MaterialApp(
title: 'dart 的基础语法',
home: FirstPage(),
));
// 匿名方法
Function fun = (str) {
print('hello---$str');
};
fun('1'); // hello---1
// 自身直接调用
((){
print('自己直接调用');
})();
List hello = ['h', 'e', 'l', 'l', 'o'];
// 匿名函数作为参数传递
// print(listTimes(hello, (str){ return str * 3; })); // [hhh, eee, lll, lll, ooo]
print(listTimes1(hello)); // [hhh, eee, lll, lll, ooo]
}
// 将自身调用三次
List listTimes(List list, String times(str)) {
for (int i = 0; i < list.length; i++) {
list[i] = times(list[i]);
}
return list;
}
// 匿名函数在方法中使用
List listTimes1(List list){
Function fun = (str) { return str * 3; };
for (int i = 0; i < list.length; i++) {
list[i] = fun(list[i]);
}
return list;
}
void main() {
runApp(MaterialApp(
title: 'dart 的基础语法',
home: FirstPage(),
));
Function fun = printCount();
fun(); // 0
fun(); // 1
}
printCount() {
int count = 0;
// 一般都使用匿方法返回闭包函数
return (){
print(count++);
};
}
通过 throw
抛出异常,通过 try-catch
(如果不知道是哪种类型) 或者 try-on
(用于精确匹配) 捕获异常。
void main() {
runApp(MaterialApp(
title: 'dart 的基础语法',
home: FirstPage(),
));
// 使用 try-on try-catch 进行捕获
try{
divide(10, 0);
}on IntegerDivisionByZeroException { // on 是知道是哪种异常的时候,精确匹配
print('Division by zero.');
} catch(e) { // 对于不确定类型的异常,可以用catch处理
print(e);
} finally { // 都会执行
print('无论什么样都执行!');
}
}
// 异常处理
divide(int a, int b) {
if (b == 0) {
// 通过throw 抛出异常
throw new IntegerDivisionByZeroException(); // 抛出的是自带的异常
// throw new Exception('Divide by zero'); // 抛出字符串异常
} else {
return a / b;
}
}
//一个简单的类
class Point {
num x;
num y;
}
void main() {
var point = Point();
point.x = 4; // Use the setter method for x.
assert(point.x == 4); // Use the getter method for x.
assert(point.y == null); // Values default to null.
}
class Cat {
String name;
int age;
Cat(String name, int age) {
this.name = name;
this.age = age;
}
}
void main() {
Cat Tom = new Cat("Tom", 2);
print(Tom.name);
}
class Cat {
String name;
int age;
Cat(this.name ,this.age)
}
void main() {
Cat Tom = new Cat("Tom", 2);
print(Tom.name);
}
class Cat {
String name;
int age;
Cat(this.name ,this.age)
Cat.newBorn(){
name = "Cherry";
age = 0;
}
}
void main() {
Cat Cherry = new Cat.newBorn();
print(Cherry.name);
}
类的实例是由 方法和实例变量 组成的;
使用类的成员;
通过 .
引用实例的变量或者方法,可以使用 ?.
;
var p = new Person('lily');
print(p.name);
p?.name = 'wanghogn'; // 如果p不是null,把值设置为wanghogn
import 'package:flutter/material.dart';
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Dart 的基础语法练习'),
),
body: Center(
child: Text('1212'),
),
);
}
}
void main() {
runApp(MaterialApp(
title: 'dart 的基础语法',
home: FirstPage(),
));
Person p = new Person.born();
print(p.name); // lily 这样的好处感觉在定义的时候不用初始化,在内部已经写好了,不错
Person p1 = new Person.middle();
print(p1.age); // 16
Man man1 = new Man.small('honhong');
print(man1.age);
Man man2 = new Man.born();
print('${man2.name}, ${man2.age}'); // man 0
}
class Person {
String name;
int age;
Person(this.name, this.age);
Person.born(){
name = 'baby';
age = 0;
}
Person.middle() {
name = 'junior';
age = 16;
}
}
// 实现继承,实现继承,在里面不用定义变量,父类命名的构造函数不会被子类继承,要使用:调用
class Man extends Person {
Man(String name, int age):super(name, age);
// 调用自身的构造函数,自身又调用父类的构造函数
Man.small(String name):this(name, 0);
Man.old(String name):this(name, 45);
Man.born():super.born(){
name= 'man';
}
}
void main() {
runApp(MaterialApp(
title: 'dart 的基础语法',
home: FirstPage(),
));
Point dot = new Point(2, 4);
print(dot.sum); // 6
}
class Point {
final num x;
final num y;
final num sum;
// 可以在构造函数主体运行之前初始化实例变量,初始值设定项用逗号分开
// 这个挺好的,能够通过其他的变量来初始化其他的变量
Point(x, y)
: x = x,
y = y,
sum = x + y;
}
void main() {
runApp(MaterialApp(
title: 'dart 的基础语法',
home: FirstPage(),
));
Person xiaoming = new Person.born();
xiaoming.run(); // i can run
}
class Person {
String name;
int age;
Person(this.name, this.age);
Person.born(){
name = 'baby';
age = 0;
}
// 定义方法
run() {
print('i can run');
}
}
void main() {
runApp(MaterialApp(
title: 'dart 的基础语法',
home: FirstPage(),
));
Man xiaoming = new Man('xiaoming', 24);
xiaoming.run(); // i am a man, i can run
}
class Person {
String name;
int age;
Person(this.name, this.age);
Person.born(){
name = 'baby';
age = 0;
}
// 定义方法
run() {
print('i can run');
}
}
class Man extends Person{
Man(String name, int age):super(name, age);
// 重写方法
@override
run() {
print('i am a man, i can run');
}
}
dart中通过使用get
和set
关键字来实现getter
和setter
,get 在定义的时没有括号也没有参数;
void main() {
runApp(MaterialApp(
title: 'dart 的基础语法',
home: FirstPage(),
));
offsetPoint dot = offsetPoint(1, 2, 10);
print(dot); // offsetPoint{x=11, y=12}
print(dot.x); // 11 ,调用get
print(dot.y); // 12
dot.x = 4; // 调用set
print(dot); // offsetPoint{x=14, y=12}
}
class offsetPoint{
int _x;
int _y;
int offset;
offsetPoint(int x, int y, int offset)
: _x = x,
_y = y,
offset = offset {
print('初始化实例变量');
}
// 定义一个getter
int get x => _x + offset;
// getter 不能有参数,连括号都要删掉
int get y {
return _y + offset;
}
// 定义setter
void set x(int x) => _x = x;
void set y(int y) => _y = y;
@override
String toString() {
return 'offsetPoint{x=$x, y=$y}';
}
}
void main() {
runApp(MaterialApp(
title: 'dart 的基础语法',
home: FirstPage(),
));
Man p1 = new Man();
p1.eat(); // i eat meat;
}
// 定义一个抽象类
abstract class Person {
// 定义一个抽象方法
void eat();
}
class Man extends Person {
void eat() {
print('i eat meat');
}
}
之所以叫隐式接口,是因为**没有关键字 interface
**,一个类可以实现多个接口 class Point implements Comparable, Location {...}
;
通过在方法前面添加 static
来实现;静态方法要通过类直接调用;
mixin
关键字创建一个没有构造函数的扩展类;with
关键字后跟多个类名来使用mixinvoid main() {
runApp(MaterialApp(
title: 'dart 的基础语法',
home: FirstPage(),
));
Man p1 = new Man('lily');
p1.eat(); // i eat
}
mixin Person {
String name;
eat() {
print('i eat');
}
}
class Man with Person {
String name;
Man(this.name);
}
E
、T
、S
、K
和 V
; // 使用泛型,可以更好的生成代码;
var names = List<String>();
names.add('kathy'); // 只能添加字符串
void main() {
runApp(MaterialApp(
title: 'dart 的基础语法',
home: FirstPage(),
));
// 使用泛型,可以更好的生成代码;
var names = List<String>();
names.add('kathy'); // 只能添加字符串
DataHolder<String> data1 = new DataHolder('string 类型的');
print(data1.getData());
DataHolder<int> data2 = new DataHolder(1);
print(data2.getData());
}
// 这样写可以减少代码的重复
class DataHolder<T> {
T data;
DataHolder(this.data);
getData() {
return data;
}
setData(data) {
this.data = data;
}
}
// 使用字面量集合
List names = <String>['a', 'b', 'c'];
Map person = <String, String>{
'name': 'lily',
'age': '10',
};
var names = List<String>();
class Eeat<T extends Person> {
String toString();
}
异步库里有个概念叫做 Future
,就像是 Promise
;
import 'dart:async';
Future
创建一个异步void main() {
runApp(MaterialApp(
title: 'dart 的基础语法',
home: FirstPage(),
));
getAjoke().then((val) {
print(val); // his is a joke
}).catchError((err) {
print(err); // 'no joke for you!1'
});
}
// 使用Futter 实现异步
Future<String> getAjoke() {
return new Future<String>(() {
throw new Exception('no joke for you!1');
return 'this is a joke';
});
}
Future
模拟一个延时的方法void main() {
runApp(MaterialApp(
title: 'dart 的基础语法',
home: FirstPage(),
));
getAjoke().then((val) {
print(val); // his is a joke
}).catchError((err) {
print(err); // 'no joke for you!1'
});
}
// 使用Future 创建一个比较费时的例子
Future<String> getAjoke() {
return new Future<String>.delayed(new Duration(milliseconds: 2000), () {
return 'this is a joke';
});
}
async
方法和 await
表达式来配合实现异步,一个异步的函数可以包含多个 await
表达式;void main() async{
runApp(MaterialApp(
title: 'dart 的基础语法',
home: FirstPage(),
));
try {
String result = await getAjoke();
print(result);
} catch(e) {
print(e);
}
print('现在可以执行');
}
// 使用Future 创建一个比较费时的例子
Future<String> getAjoke() {
return new Future<String>.delayed(new Duration(milliseconds: 2000), () {
return 'this is a joke';
});
}
length
改为数组var obj = { name: 'lily', age: 32, length: 2}
var arr1 = [].slice.call(obj, 0);
1.1 使用 HTTP 协议访问web
IP 协议:
IP 协议是在源地址和目的地址之间传送一种称为数据包的东西;
TCP 协议
TCP 协议是提供可靠的字节流服务;
DNS 服务
提供域名到IP地址之间的解析服务;
在使用 HTTP 协议通信的过程中,IP 协议、TCP 协议 和 DNS 服务 起到的作用:
request headers
中看到 contention: keep-alive
字段;
contention: Keep-Alive
;Connection: close
;HTTP 协议是无状态的,为了想保留某些状态,引入了cookie;
本来想写点,但是看了一篇博客,感觉写得非常好,就把地址粘在这里吧https://segmentfault.com/a/1190000004556040
状态码 | 场景描述 |
---|---|
200 | 客户端发来的请求在服务端已正常处理 |
204 | 请求已经处理,但是没有资源可以返回 |
206 | 对资源的某一个部分进行请求 |
301 | 永久性重定向,资源的URI已经更新 |
302 | 临时重定向,资源的URI已临时定位其他资源 |
303 | 临时重定向,资源的URI已临时定位其他资源,要使用GET |
304 | 资源已经找到,但是未符合条件 |
307 | 临时重定向,资源的URI已临时定位其他资源 |
400 | 请求报文中存在语法错误 |
401 | 需要认证 |
403 | 资源不允许访问 |
404 | 没有找到资源 |
500 | 服务器内部资源出故障 |
503 | 服务器处于超负载或者停机维护 |
是客户端和服务器端的中间人,是一种有转发功能的应用程序;代理服务器的基本行为就是接收客户端发送的请求后转发(不会改变URI)给其他的服务器;
- 缓存代理
代理转发响应的时候,会预先将资源的副本保存到代理服务器上,当代理再次接收相同的资源请求时,就可以将之前缓存的资源作为响应返回。
网关是转发其他服务器通信数据的服务器。当接收到客户端的请求的时候,它就像是自己拥有其他服务器的资源一样来处理。网关能使通信线路上的协议提供非HTTP协议服务。
隧道可按要求建立起一条与其他服务器通信线路。
HTTP 首部可以在客户端和服务器端传递额外重要的信息;
Cache-Control
可以操作缓存服务器的缓存机制;
参数名称 | 作用 |
---|---|
public | 表明其他的与欧诺个户用户也可以利用的缓存 |
private | 缓存只是给指定的用户用 |
no-cache | 不缓存过期的资源(do not serve from cahce without reavalidation) |
no-store | 不进行缓存,暗示请求和响应中包含有机密信息 |
max-age=600800(秒) | 缓存的最长时间,如果有expires的时候,会忽略掉这个字段 |
min-fresh=60(秒) | 过了60秒后缓存就无法作为响应返回了 |
only-if-cached | 仅在缓存服务器本地缓存目标资源的情况下才会返回 |
must-revalidate | 代理会向源服务器再次验证即将返回的响应 |
no-transform | 缓存不能改变主体的媒体类型 |
Connection
参数名称 | 作用 |
---|---|
close | 首部字段不再转发给代理 |
Keep-Alibe | 缓存只是给指定的用户用 |
Date
HTTP 报文的日期和时间
Transfer-Encoding
采用哪种编码方式进行发送
参数名称 | 作用 |
---|---|
chunked | 采用分块的方式 |
Via
报文经过的传输路径(网关或者代理)
Warning
通知用户一些和缓存相关的问题警告;
警告码 | 警告内容 | 说明 |
---|---|---|
110 | 响应已过期 | 代理返回已过期的资源 |
111 | 再验证失败 | 代理再次验证资源的有效性的时失败(服务器无法到达等原因) |
112 | 断开连接操作 | 代理与互联网的连接故意被切断 |
113 | 试探性过期 | 响应超过最大限定时间 |
119 | 杂项警告 | 任意的警告内容 |
214 | 使用了转换 | 代理对内容编码或者媒体类型执行了某些操作 |
299 | 持久杂项警告 | 任意的警告内容 |
Accept: text/html; q=0.3; text/plian
Accept-Charset: iso-8859-5, unicode-1-1; q=0.8
通知服务器,用户代理支持的内容编码以内容编码的优先级顺序;
Accept-Encoding: gzip
名称 | 说明 |
---|---|
gzip | 由文件压缩程序gzip生成的编码格式 |
identity | 不执行压缩或不会变化的默认格式 |
Accept-Language: zh-cn
Host: www.hackr.jp
if-modified-since
字段指定的日期时间后,资源发生了更新,服务器会接受请求;range: bytes=5001-10000
请求获取从第5001字节到10000字节的资源;Age
告诉客户端,源服务器在多久前创建了响应。zi'duan'z字段值的单位为秒;
Etag
可将资源以字符串的形式做唯一性标识的方式,资源的URI虽然没有变,但是当资源更新后,ETAG值会改变;
Server
告诉客户端当前服务器上安装的HTTP服务器应用程序的信息。
实体首部字段
Allow
告诉客户端能够支持的所有HTTP方法;
Content-Length
实体主体部分的大小,** 对实体主体进行内容编码传输的时候,不能再使用content-length;
为cookie 服务的首部字段
字段名 | 说明 | 首部类型 |
---|---|---|
set-cookie | kai'shi开始状态管理所使用的cookie信息 | 响应首部字段 |
cookie | 服务器接收到的cookie信息 | 请求首部字段 |
set-cookie 字段的属性
属性 | 说明 |
---|---|
name=value | 给cookie的名称和值 |
expires=date | cookie的有效期 |
path=Path | 限定cookie 的发送范围的文件目录 |
domain=域名 | 作为cookie适用对象的域名 |
secure | 仅在HTTP安全通信时才会发送cookie |
HttpOnly | 加以限制,使Cookie不能被javascript脚本访问 |
https的通信流程(阮大神的一篇博文):http://www.ruanyifeng.com/blog/2011/08/what_is_a_digital_signature.html
SSL客户端认证和表单认证结合,SSL客户端认证来认证客户端的计算机,而表单认证来确定用户本人的行为;
Ajax的核心技术是XMLHttpRequest的API,通过javascript的脚本的调用就能和服务器进行HTTP通信了,借由这种手段,就能从已经加载完毕web页面上发起请求,只更新局部页面。
使用延迟应答,模拟实现服务器向客户端进行推送的功能。
通常,服务器端接收到请求,在处理完毕后就会立即响应,但是为了实现推送功能,Comet会先将响应置于挂起的状态,等服务器端有内容更新的时候,再返回响应。
SPDY是Google 开发的基于传输控制协议 (TCP) 的应用层协议 ,开发组正在推动 SPDY 成为正式标准(现为互联网草案)。
在应用层和传输层之间增加了一个二进制分帧层,以达到“在不改动HTTP语义,HTTP方法、状态码、URI机及首部字段的情况下,突破HTTP/1.1 的性能限制,改进传输性能,实现低延迟和高吞吐量”;
在二进制分帧层上,HTTP2.0 会将所有的信息分割为更小的消息和帧,并对它们采用二进制格式的编码,其中HTTP1.X 的首部信息会被封装到Headers帧,而request body则封装到Data帧里。
HTTP2.0 通信都在一个连接上完成,这个连接可以承载任意数量的双向数据流。每个数据流以消息的形式发送,而消息又是由一个或者多个帧组成,这些帧可以乱序发送,然后再根据每个帧首部的流标识符重新组装;
HTTP/2.0规定了在客户端和服务器端会**使用并且维护「首部表」**来跟踪和存储之前发送的键值对,对于相同的头部,不必再通过请求发送,只需发送一次。
如果请求中不包含首部,那么首部的开销就是零字节,此时所有的首部都自动使用之前请求发送的首部;如果首部发变化了,那么只需要发送变化了数据在Headers帧里面,新增或者修改的首部帧会被追加到“首部表”。首部表在HTTP2.0连接存续4qi期内始终的存在,由客户端和服务器共同渐进地更新。
HTTP2.0所有的通信都是在一个TCP连接上完成的。HTTP2.0 把 HTTP 协议通信的基本单位缩小为一个个的帧,这些帧对应着逻辑流中的消息。**这些帧并行地同在一个TCP连接上双向交换消息。**也就是说同一链接上有多个不同方向的数据流在传输。客户端可以一边乱序发送streams,也可以一边接收服务器的响应,而服务器端同理。
优点
HTTP性能的关键在于低延迟,而不是高带宽。大多数的HTTP连接的时间短,而且是突发性的,但是TCP只在长时间连接传输大块数据时效率才最高。HTTP2.0 通过让所有的数据流公用一个连接,可以更有效地使用TCP连接,让高带宽也能真正服务于HTTP的性能。
同时,单链接多资源的方式,有如下好处:
可以减少服务器的压力,内训占用少,连接的吞吐量大;
由于 TCP 连接减少,使得网络阻塞的情况得以改观;
慢启动时间减少,拥塞和丢包恢复速度更快。
因此, “资源合并减少请求”的优化手段对于HTTP2.0来说是没有效果的。
既然所有资源都是并行发送,那么就需要“优先级”的概念了,这样就可以对重要的文件进行先传输,加速页面的渲染。
在 HTTP2.0中,服务器推送是指客户端请求之前发送数据的机制。
虽然 HTTP2.0 协议并没有声明一定要使用SSL,但是Google Chrome 等浏览器强制要使用 HTTP2.0 必须使用SSL。
JavaScript 诞生于 1995年,是一种专为与网页交互而设计的脚本语言。由三种不同部分组成:
ECMScript 是这本语言的基础,规定了这门语言的下列组成部分:
文档对象模型(DOM, Document Object Model) 是针对XML但经过扩展用于HTML的应用程序编程接口。提供了以下方法和接口的定义:
浏览器对象模型(BOM, Browser Object Model) 用来访问和操作浏览器窗口,有以下的扩展:
在 HTML 内部直接写入 <script>
片断,这种写法要注意JavaScript 是单线程的,如果在初始化页面的时候,你的代码中存在大量同步运行的代码,导致 JS 线程一直处于繁忙状态,这时候用户在页面上进行交互时将不会得到任何反应,就像是卡死了一样。想了解具体原因,参考文章浏览器渲染原理
<script src="example.js" src="defer"></script>
ESCAScript 的变量是 松散类型 的,就是说可以用来保存任何类型的数据。也就是说一个变量就是一个占位符。
var a = 'hi';
a = 10;
定义变量可以使用 var 操作符,定义多个变量,每个变量之间用 逗号 分开,如果 省略 var 操作符可以定义 全局变量,未经初始化的变量,会保存一个特殊的值—— undefined
function show() {
var _message = '局部变量',
_name = 'lily';
g_message = '全局变量';
}
show();
console.log(g_message); // 全局变量
console.log(_message); // _message is not defined
在 ESCScript 中变量会被自动 提升
console.log(enhance); // 变量提升
var enhance = "变量提升";
ECMAScript 有6种数据类型,其中5种基本的数据类型: Undefined、Null、Boolean、Number和String,还有1种复杂的数据类型: Object。一般使用 typeof 检查5种基本的数据类型,使用 instanceof 检查是哪种复杂的数据类型。
typeof
常见的就不说了,说几个可能容易混或者错的。
值 | 使用typeof操作符返回 |
---|---|
值未初始化 | 'undefined' |
值对象、null | 'Object' |
值是函数 | 'function' |
// 未初始化的变量
var uninital;
console.log('未初始化的变量:---' +typeof uninital); // undefined
// 未定义的变量
console.log('未定义的变量:---' + typeof liy); // undefined
// 值为null
var obj = null;
console.log('值是null的时候:---' + typeof obj); // object
// 值未函数
function add (a, b) {
return a + b;
}
console.log('值是funciton:---' + typeof add); // function
Boolean 类型
Boolean 类型是 ECMAScript 中使用最多的一种类型,只有两个字面量值: true、false;区分大小写,也就是说 True 不是布尔值。
流程控制语句(例如 if 语句)自动执行 转型函数 Boolean()。
var message = "hello world";
if (message) {
console.log('转换为 true'); // 执行这句
} else {
console.log('转换为 false');
}
下表给出了几种数据类型及其对应的转换规则
数据类型 | 转换为true 值 | 转换为false值 |
---|---|---|
Boolean | true | false |
String | 任何非空字符串 | ''(空字符串) |
Number | 任何非0数字(包括无穷大) | 0、NaN |
Object | 任何对象 | null |
Undefined | 不适用 | undefined |
Number 类型
var result = Number.MAX_VALUE + Number.MAX_VALUE;
console.log('数值是否是有穷的:' + isFinite(result)); // false
console.log(isNaN(10)); // false
console.log(isNaN('10')); // false
console.log(NaN == NaN); // false
console.log(Number('hello')); // NaN
console.log(Number('')); // 0
String 类型
转换为字符串有两种方式 toString([几进制]) 和 String(名)
var num = 10;
console.log(num.toString()); // '10'
console.log(num.toString(2)); // '1010'
console.log(String(10)); // '10'
Object 类型
访问对象的属性有两种方式
person['name']
person.name
布尔操作符
逻辑非和逻辑或都是 短路 操作符
逻辑与
如果第一个操作数是对象,则返回 第二个 操作数;
如果第二个操作符是对象,则只有第一个操作数的求值结果是 true 的情况下才会返回该 对象;
var obj = {
name: 'lily',
}
console.log(obj && true); // true
console.log(true && obj); // {name: 'lily'}
逻辑与
如果第一个操作数是对象,则返回 第一个 操作数;
如果两个操作数都是对象,则返回 第一个 操作数;
关系操作符
如果两个操作数都是字符串,则比较两个字符串对应的 字符编码,所以字符串比较的时候一般都转换为大写(或者小写);
任何操作数与 NaN 进行比较,结果都是 false;
console.log('Brink'.toLowerCase() < 'alpha'.toLowerCase()); // false
console.log(NaN < 3); // false
for 语句
在 ECMAScript 中没有块级作用域的概念,因此循环内部定义的变量也可以在外部访问到。
for (var i = 0;i < 10 ; i++) {}
console.log(i); //10
其实以上的代码就相当于
var j;
for (j = 0; j < 10; j ++) {}
console.log(j);
for-in 语句
for-in 语句用来遍历对象的属性,但此时的对象不能是null或者undefined。
switch 语句
switch 语句在比较值的时候使用的是全等
看一个我觉得很新颖的语句
var num = 18;
switch (true) {
case num < 0:
alert('less than 0');
break;
case num > 0 && num < 10:$
alert('between 0 and 10');
breka;
default:
alert('more than 10');
}
函数
可以向 ECMAScript 函数传递任意数量的参数,并且可以通过arguments对象来访问这些参数。即便你定义的函数只接收两个参数,在调用这个函数也未必一定传递两个参数,可以传递一个、三个甚至不传递参数。如果没传递值的命名参数将自动被赋予undefined值。
function add() {
if (arguments.length == 1) {
alert(arguments[0] + 10);
} else {
alert(arguments[0] + arguments[1]);
}
}
add(10); // 20
ECMAScript 变量包含两种不同类型的值:基本类型和引用类型值。
基本类型的值
引用类型
- 执行环境
执行环境定义了变量或函数有权访问的其他数据,决定了他们的行为;
执行环境可以分为**全局执行环境** 和 **每个函数自己的执行环境**;
- 作用域链
代码在执行环境中执行,会创建对象的一个 **作用域链**,这个作用域链可以保证对执行环境有权访问的所有变量和函数的有序访问。
优化内存占用的最佳方式,就是为代码只保存必要的数据。一旦数据不再用了,最好通过将其值设置为 null 来释放引用。
function cretePerson(name) {
var localPerson = new Object();
localPerson.name = name;
return localPerson;
}
var g_person = cretePerson('lily');
g_person = null;
在 ECMAScript中,引用类型是一种数据结构,常用的有:Object、Date、RegExp、Function、基本包装类型、单体内置对象。
插入
arr[length] = 'add data from bottom';
arr.push('add data from bottom');
arr.unshift('add data from head');
splice(2,0,'red','green');
,三个参数,第一个是位置,第二个是要删除的数据个数,第三个数据为插入的数据;删除
arr[length] = arr.length - 1;
arr.pop();
arr.shift();
arr.splice(0,1);
,两个参数,第一个参数是位置,第二个参数是要删除的个数;替换
arr.splice(2,1,'red')
检测数组
检测是否是数组
if(Array.isArray(value)){}
转换为字符串的方法
排序方法
合并
var color1 = ['color1', 'color2'];
var color2 = color1.concat('yellow'); // color1,color2,yellow
位置方法
迭代
10.归并数组
- reduce
- reduceRight
函数分为函数声明和函数表达式,函数表达式可以随时访问,因为函数声明提升。
arguments 对象
arguments 对象主要用途是保存函数参数,包含一个特殊属性callee属性,是个指针,指向拥有这个arguments对象的函数;
function factorial (num) {
if (num < 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}
console.log(factorial(4)); //24
this 对象
this 引用的是函数执行环境对象
window.color = 'red';
var obj = {color: 'obj bule'};
function showColor () {
console.log(this.color);
}
showColor(); // red
obj.sayColor = showColor;
obj.sayColor(); // obj bule
apply(运行函数的作用域, 参数数组)
window.color = 'window color red';
var obj = {color: 'obj color blue'};
function showColor(data) {
console.log(this.color); //window color red
console.log(`传入数据${data}`);
}
showColor();
showColor.apply(obj, ['我用了apply']);
// obj color blue 传入数据我用了apply
call(运行函数的作用域,其余参数)
window.color = 'window color red';
var obj = {color: 'obj color blue'};
function showColor(data) {
console.log(this.color); //window color red
console.log(`传入数据${data}`);
}
showColor.call(obj, '我用了call');
//obj color blue
//传入数据我用了call
1. length
2. charAt(位置)
3. charCodeAt(位置):返回字符编码
4. substring(start, [end])
5. substr(start, [end])
6. slice(start, [end])
7. indexof(要查找内容,位置)
8. trim: 去除空格
9. toUpperCase
10. toLocaleUpperCase
11. toLowerCase
12. toLocalLowerCase
13. split(切割符),返回数组
今天分享一个ECMAScript的continuous,文章最下面附了原文链接。
文章的题目:“Flatten Arrays in Vanilla JavaScript with flat() and flatMap()”很好的解释了flat()
和flatMap()
的作用,就是用来 展开数组,并且是不用任何第三方库;直接上例子
const animals = [['🐕', '🐶'], ['😺', '🐈']];
const flatAnimals = animals.flat();
// same as: const flatAnimals = animals.flat(1);
console.log(flatAnimals);
// ['🐕', '🐶', '😺', '🐈']
flat()
方法的深度的时候:const animals = [['🐕', '🐶'], ['😺', '🐈', ['😿',['🦁'], '😻']]];
const flatAnimals = animals.flat(2);
console.log(flatAnimals);
// ['🐕', '🐶', '😺', '🐈', '😿',['🦁'], '😻']
flat
的参数设置为Infinity
const animals = [['🐕', '🐶'], ['😺', '🐈', ['😿',['🦁'], '😻']]];
const flatAnimals = animals.flat(Infinity);
console.log(flatAnimals);
// ['🐕', '🐶', '😺', '🐈', '😿', '🦁', '😻']
flatMap()
就是对数组的每个值先执行map
方法,然后再对形成的数组执行flat(1)
的方法;
const animals = ['🐕', '🐈', '🐑', '🐮'];
const noises = ['woof', 'meow', 'baa', 'mooo'];
const mappedOnly = animals.map((animal, index) => [animal, noises[index]]);
const mappedAndFlatten = animals.flatMap((animal, index) => [animal, noises[index]]);
console.log(mappedOnly);
// [['🐕', 'woof'], ['🐈', 'meow'], ['🐑', 'baa'], ['🐮', 'mooo']
console.log(mappedAndFlatten);
// ['🐕', 'woof', '🐈', 'meow', '🐑', 'baa', '🐮', 'mooo']
BOM: 在 Web 中使用 javascript的核心。
在 2013 年以前,对于数据流的控制一直以来是使用的 MVC(Model-View-Controller) 架构模式来进行数据的管理:
但是对于非常巨大的代码库和庞大的组织来说,MVC 很快就会变得非常复杂。每当工程师需要新增一个功能的时候,对代码的修改可能带来新的 bug,不同模块之间的依赖关系会变得“脆弱而且不可预测”。如图2:
基于以上的情况,Facebook 公司推出了 Flux 框架,用来管理数据,相比于 MVC ,它是一种更严格的数据流控制。
一个 Flux 包含四个部分,如下:
当用户请求一个动作,会触发 Action,之后 Action e驱动 Dispatcher 来进行分发 Action 操作,从而更新 Store 中的数据,Store 中数据改变后,就会更新 View 的展示。
Flux 虽然很好,也有不足之处,比如说 难以进行服务端渲染、Store 混杂了逻辑和状态等,但是这种 单一数据流 的理念衍生出了像 Redux 和 MobX 框架的实现。本篇文章着重讲述 MobX。
MobX 通过 响应式编程(在命令式编程环境中,a := b + c
表示将表达式的结果赋给 a,而之后改变 b 或者 c 的值不会影响 a 的值,但在响应式编程中, a 的值会随着 b 或 c 的值得改变而改变)的**来管理数据。MobX 也是支持单向数据流的,是通过 action 触发 state 的变化,进而触发 state 的衍生对象(Computed 和 Reactions)。所有的衍生默认都是同步更新的。
ESNext 中新增了 decorator 属性,所谓装饰器,可以简单的理解为 锦上添花;以钢铁侠为例,钢铁侠本质上是一个人,只是装饰了很多的武器以后才变得很 NB ,不过怎么装饰他还是一个人。
function decorateArmour(target, key, descriptor) {
const method = descriptor.value;
let moreDef = 100;
let ret;
descriptor.value = (...args)=>{
args[0] += moreDef;
ret = method.apply(target, args);
return ret;
}
return descriptor;
}
class Man{
constructor(def = 2,atk = 3,hp = 3){
this.init(def,atk,hp);
}
@decorateArmour //这个就是使用了装饰器
init(def,atk,hp){
this.def = def; // 防御值
this.atk = atk; // 攻击力
this.hp = hp; // 血量
}
toString(){
return `防御力:${this.def},攻击力:${this.atk},血量:${this.hp}`;
}
}
var tony = new Man();
console.log(`当前状态 ===> ${tony}`);
// 输出:当前状态 ===> 防御力:102,攻击力:3,血量:3
tips:
ES7 中的 decorator 其实是一个语法糖,不过依赖于 Object.defineProperty(obj, prop, descriptor)
在 MobX 中, State 就对应业务的原始状态,可以通过 observable
或者 @observable
将这些状态变为可观察的,顾名思义,可观察数据就是 当数据变化的时候,可以被观察到。
objects
arrays
maps
用 observable
转换为可观察数据,而 原始数据 用 observable.box
转化为可观察数据;
普通object
普通对象和非普通对象的划分就是看对象是否有原型,如果没有原型或者原型是Object.prototype 的对象,那么就是普通对象;
const person = observable({
name: 'lily',
age: 26,
address: {
province: '天津',
}
});
console.log('打印经过observable修饰后的对象---', person);
//Proxy {Symbol(mobx administration): ObservableObjectAdministration$$1}
// 默认第一次会执行一次
autorun(() => {
console.log(`name: ${person.name}-- age:${person.age}--address:${JSON.stringify(person.address)}`);
});
// 改变name的值
person.name = '石丽丽';
//name: 石丽丽-- age:26--address:{"province":"天津"}
// 改变 address 的province的值, 说明会递归遍历整个对,即使属性还是个对象;
person.address.province = '北京';
//name: 石丽丽-- age:26--address:{"province":"北京"}
// 新增的属性是不可以被观察的,可以使用 extendObservable
person.obj = "web developer";
// 没有打印的结果
// name: 石丽丽-- age:26--address:{"province":"北京"}--obj:web developer--extendObj:undefined
// 使用 extendObservable
extendObservable(person, {
extendObj: 'extend web developer'
})
// name: 石丽丽-- age:26--address:{"province":"北京"}--obj:web developer--extendObj:extend web developer
tips: 对于新增的属性,不可以被观察,如果需要被观察需要用 extendObservable
或者set
;observable 会递归遍历整个对象,即使这个属性还是个对象;
非普通对象
observable 会返回一个特殊的boxed values 类型的可观测的对象,返回的 boxed values 对象并不会把非普通对象的属性转换为可观测的,而是保存一个指向对象的引用;这个引用是可观测的;对原对象的访问和修改可以通过 get()
和 set()
方法操作。
function Person(name, age) {
this.name = name;
this.age = age;
}
const person = observable.box(new Person('lily', 26));
autorun(() => {
console.log(`name: ${person.get().name}, age: ${person.get().age}`);
});
// 改变属性
person.get().name = 'wanghong';
// 不会打印
// 改变引用
person.set(new Person('wanghong', 27));
//name: wanghong, age: 27
对于非普通的对象的属性,可以通过以下的方式将其变为可观察的:
//将非普通对象的属性变为可观察的
function Person(name, age) {
// 使用 extendObservable 在构造函数中创建可观察的属性
extendObservable(this, {
name: name,
age: age,
})
}
const person = new Person('extendlily', 28);
autorun(() => {
console.log(`使用extendObservable创建可观察的属性--name:${person.name},age:${person.age}`);
});
// 改变对象的属性
person.name = '王宏';
// 使用extendObservable创建可观察的属性--name:王宏,age:28
这种方式比较麻烦,所以推荐使用装饰器的方式,这种方式的好处还在于对于原始数据类型的数据的话,自己内部有判断,不用使用observable.box()
如下:
// 非常推荐的一种方式,使用 @observable 装饰器
class Person {
@observable name;
@observable age;
constructor(name, age) {
this.name = name;
this.age = age;
}
}
const person = new Person('@lily', 20);
autorun(() => {
console.log(`使用装饰器修饰的--name:${person.name},age:${person.age}`);
})
// 改变可观察的属性
person.name = 'wanghong';
// 使用装饰器修饰的--name:wanghong,age:20
arrays
const arr = observable([1,2,3]);
console.log(`用observable修饰的数组`, arr);
autorun(() => {console.log(`arr--, ${arr}`)})
// 判断是不是一个数组
arr.push(4);
// arr--, 1,2,3,4
tips: 判断是不是数数组的两种方式: Array.isArray(observable([]).slice())
和 isArrayLike(arr)
;
maps
const map = observable.map({ key: "value" });
autorun(() => {
console.log(`map:${map.get('key')}`);
})
// 改变key的值
map.set("key", "new value");
// map:new value
tips: Map 对象的每个对象都是可观测的,而且向Map对象中添加或删除元素的行为也是可以被观测的;
原始数据类型
对于原始数据的话,通过 get()
获取数据,通过 set()
设置数据;
const cityName = observable.box('Vienna');
console.log(cityName.get()); // Vienna
MobX 中四种方式对 observables 做出响应,分别为 @computed
autorun
when
reaction
,接下来会分别介绍这四种方式的使用场景:
Computed values are values that can be derived from the existing state or other computed values. Conceptually, they are very similar to formulas in spreadsheets. Computed values can't be underestimated, as they help you to make your actual modifiable state as small as possible. Besides that they are highly optimized, so use them wherever possible.
计算值(computed values)是可以根据现有的状态或其它计算值衍生出的值。 概念上来说,它们与excel表格中的公式十分相似。 不要低估计算值,因为它们有助于使实际可修改的状态尽可能的小。 此外计算值还是高度优化过的,所以尽可能的多使用它们。
以上的这句话是 MobX 官网的原话,这段话充分的说明了 MobX 的使用场景和重要性。Mobx 是纯函数,不能改变state的状态,computed value 采用的是延迟更新,computed values are automatically derived from your state if any value that affects them changes。如果一个计算值不再被观察了,例如使用它的UI不复存在了,MobX 可以自动地将其垃圾回收。
class Squared{
@observable length = 2;
constructor(length) {
this.length = length;
}
@computed get squared() {
return this.length * this.length;
}
}
const square = new Squared(2);
// 改变长度
square.length = 3;
// Squared的面积:9
autorun(() => {
console.log(`Squared的面积:${square.squared}`)
});
autorun
顾名思义,就是自动执行,当使用 autorun 时,所提供的函数总是立即被触发一次,然后每次它的依赖关系改变时会再次被触发。相比于 computed,他不会产生一个新的值,它更像是发起请求这样的命令式副作用;它会返回一个清楚函数 disposer,当不需要观察相关的 state 变化的时,可以调用 disposer 函数清除副作用。
const number = observable([1,2,3]);
const sum = computed(() => number.reduce((a, b) => a + b), 0);
const disposer = autorun(() => {console.log(sum.get())});
number.push(4);
// 10
// 清除副作用
disposer();
number.push(5);
// 不执行
when
when(predicate: () => boolean, effect?: () => void, options?)
,predicate 会自动响应它使用的任何的state
的变化,当predicate
返回ture 的时候,函数effect会执行,且执行一次。when
也返回一个 disposer
函数。when 非常的适合用在以影响式的方式执行取消或者清楚逻辑的场景;
componentDidMount() {
class showDetail{
@observable first = 2;
@observable second = 3;
@computed get isVisible() {
return (this.first * this.second) > 10
}
constructor() {
when(
() => this.isVisible,
() => this.dispose(),
)
}
dispose() {
console.log('这里做清理工作');
}
}
const demo = new showDetail();
demo.second = 10;
// 这里做清理工作
```
reaction(() => data, (data, reaction) => { sideEffect }, options?)
,它接收两个函数参数,第一个(数据函数)是用来追踪并返回数据作为第二个函数(效果函数)的输入,第二个函数 reaction 会返回一个清楚函数 disposer。第一个函数是返回需要被观察的数据,第二个函数接收这个需要被观察的数据,同时传入 reaction,当被观察的数据改变的时候,就会触发 reaction,这样就不像 autorun
似的,当一个状态改变的时候就会触发,从而建立和要被观察的数据和reaction之间的关系;总结来说,相较于autorun,reaction 可以对跟踪哪些对象有更多的控制;
const todos = observable([
{
title: 'Java',
done: true,
},
{
title: 'javascript',
done: false,
}
]);
// 对 length 做出反应
const resLen = reaction(
() => todos.length,
length => console.log(`对长度做出反应:${todos.map(todo => todo.title).join(',')}`)
)
// 对 length 和 title 的变化作出反应
const resTitle = reaction(
() => todos.map(todo => todo.title),
titles => console.log(`对标题做出反应:${titles.join(',')}`)
);
// autorun 对任何可观察数据做出反应
const resAll = autorun(
() => console.log(`autorun 对任何的变化做出反应: ${todos.map(todo => todo.title).join(',')}`)
)
// 改变todos的长度
todos.push({
title: 'C++',
done: false,
});
/**
* 对长度做出反应:Java,javascript,C++
* Reaction.jsx:26 对标题做出反应:Java,javascript,C++
* Reaction.jsx:31 autorun 对任何的变化做出反应: Java,javascript,C++
* */
// 改变 title
todos[0].title = 'Make tea';
/**
* 对标题做出反应:Make tea,javascript,C++
* Reaction.jsx:31 autorun 对任何的变化做出反应: Make tea,javascript,C++
* */
官方建议修改 observables 或者具有副作用的函数使用 @action
,简单的说就是对于修改可观察的数据,建议使用 @action
。
@action 和 @action.bound
action
装饰器/函数遵循 javascript 中标准的绑定规则。但是,action.bound 可以用来自动地将动作绑定到目标对象。 注意,与 action 不同的是,(@)action.bound 不需要一个name参数,名称将始终基于动作绑定的属性。
class Ticker{
@observable tick = 0;
@computed get ifDispose() {
return this.tick >= 10;
}
// 使用@action.bound 绑定的this永远是正确的
@action.bound
// 使用action的话就不会增加,因为此时的this是window
// @action
increment() {
this.tick ++;
}
}
const ticker = new Ticker();
const disposer = autorun(() => {console.log(`tick: ${ticker.tick}`)})
when(
() => ticker.ifDispose,
() => disposer(),
)
setInterval(ticker.increment, 1000)
asny actions
action
包装/装饰器只会对当前运行的函数作出反应,而不会对当前运行函数所调用的函数(不包含在当前函数之内)作出反应! 这意味着如果 action 中存在 setTimeout、promise 的 then 或 async 语句,并且在回调函数中某些状态改变了,那么这些回调函数也应该包装在 action 中。
使用 action
这里列出了比较常见的一种方式,就是使用 action
进行包装:
```
class Store {
@observable students = [];
@observable loadState = 'pending';
@action
fetchData() {
this.students = [];
this.loadState = 'pending';
rest.fetchActionData({}).then(
// 内联创建动作
action('fetchSuccess', response => {
if (response.code === '0' && response.result) {
this.students = [...response.result];
this.state = 'done';
}
}),
action('fetchError', error => {
this.state = 'error';
})
)
}
}
const store = new Store();
autorun(() => {
console.log(`store中的students: ${JSON.stringify(store.students)}`);
})
store.fetchData();
```
使用 runInAction
工具函数
这种模式的优点在于你可以不用到处的写action
,而仅仅在整个过程结束的时候对状态进行修改:
class Store {
@observable students = [];
@observable loadState = 'pending';
@action
fetchData() {
this.students = [];
this.loadState = 'pending';
rest.fetchActionData({}).then(
response => {
if (response.code === '0' && response.result) {
// 将最终的修改放在一个异步的操作中
runInAction(() => {
this.students = [...response.result];
this.loadState = 'done';
})
}
},
error => {
runInAction(() => {
this.loadState = 'error';
})
}
)
}
}
const store = new Store();
autorun(() => {
console.log(`store中的students: ${JSON.stringify(store.students)}`);
})
store.fetchData();
flows
However, a nicer approach is to use the built-in concept of flows. They use generators. Which might look scary in the beginning, but it works the same as async / await. Just use function * instead of async and yield instead of await. The advantage of flow is that it is syntactically very close to async / await (with different keywords), and no manually action wrapping is needed for async parts, resulting in very clean code.
flow can be used only as function and not as decorator. flow integrates neatly with MobX development tools, so that it is easy to trace the process of the async function.
然而,更好的方式是使用 flow 的内置概念。它们使用生成器。一开始可能看起来很不适应,但它的工作原理与 async / await 是一样的。只是使用 function * 来代替 async,使用 yield 代替 await 。 使用 flow 的优点是它在语法上基本与 async / await 是相同的 (只是关键字不同),并且不需要手动用 @action 来包装异步代码,这样代码更简洁。
flow 只能作为函数使用,不能作为装饰器使用。 flow 可以很好的与 MobX 开发者工具集成,所以很容易追踪 async 函数的过程
```
class Store {
@observable students = [];
@observable loadState = 'pending';
fetchData = flow(function * () { // 这是一个生成器函数
this.students = [];
this.loadState = 'pending';
try {
const response = yield rest.fetchActionData(); // 获取resolve 解析的值
// 异步代码会被自动的包装成动作
if (response.code === '0' && response.result) {
this.students = [...response.result];
this.state = 'done';
}
} catch (error) {
this.state = 'error';
}
})
}
const store = new Store();
autorun(() => {
console.log(`store中的students: ${JSON.stringify(store.students)}`);
})
store.fetchData();
```
mobx-react
顾名思义,是联系 Mobx
和 React
之间的桥梁,从而更方便的使用 Mobx
在 React
中开发,经常的用到有:Provider
inject
observer/@observer
:
Provider
Provider 是一个 React 组件,利用 Reacxt 的 context
机制把应用所需的 state
传递给子组件。
inject
inject 是个高阶组件,和 Provider
结合使用,用于从 Provider
提取所需的 state
,作为 props
传递给目标组件。
observer/@observer
observer
函数/装饰器可以用来将React组件转变为响应式组件。它用mobx.autorun
包装了组件的render
函数以确保任何组件渲染中使用的数据变化时都可以强制刷新组件。
/**
* App.jsx 文件
**/
import React, { Component } from 'react';
import { Provider } from 'mobx-react';
import TodoList from './pages/todoList/TodoList';
import stores from './mobx/stores/index';
class App extends Component {
render() {
return (
<Provider {...stores}>
<TodoList />
</Provider>
);
}
}
export default App;
/**
* TodoList.jsx 文件
**/
import React, { Component } from 'react';
import { observer, inject } from 'mobx-react';
import TodoFilterForm from './TodoFilterForm';
import TodoMain from './TodoMain';
import TodoFilter from './TodoFilter';
import '../../styles/todoList.css';
// Provider 配合 inject 引入 state
// 无论有多少修饰器,@observer 永远在第一个
@observer
@inject('todoStore')
class TodoList extends Component {
render () {
const { todoStore } = this.props;
return (
<div className="todo-list">
<TodoFilterForm todoStore = {todoStore}/>
<TodoMain todoStore = {todoStore}/>
<TodoFilter todoStore = {todoStore}/>
</div>
)
}
}
export default TodoList;
MobX 中大量的使用了 ES.Next 中的装饰器语法,为了在新搭建的项目中支持这种语法,有两种实现方式:
使用create-react-app project-name --scripts-version custome-react-scripts
创建项目,这种方式创建的项目,支持 修饰器语法、Less、Sass;
仍然使用create-react-app project-name
创建项目,然后执行yarn run eject
弹射出配置文件,然后安装 yarn add babel-plugin-transform-decorators-legacy -D
修改 webpack 的配置文件,添加
"babel": {
"plugins": [
"transform-decorators-legacy"
],
"presets": [
"react-app"
]
}
以上的内容配置好了以后,还要通过 yarn add mobx-react mobx -S
安装 mobx-react
;
因为代码比较多,所以直接上github的地址:https://github.com/ycshill/shared/tree/master/mobx-share
toJS(value, options)
递归地将一个observable对象转换为javascript结构。支持observable数组、对象、映射和原始数据类型
const person = observable({
name: 'lily',
age: 26,
});
console.log(`没有时候用toJS转化时候的对象--`, person);
// 没有时候用toJS转化时候的对象-- Proxy {Symbol(mobx administration): ObservableObjectAdministration$$1}
console.log(`通过toJS转化后的对象---`, toJS(person));
// 通过toJS转化后的对象:{"name":"lily","age":26}
mobx-react-devtools
mobx-react-devtools 是一个用来调试 MobX + React 项目的工具,可以追踪组件的渲染以及组件依赖的可观测数据。
安装
yarn add mobx-react-devtools -D
代码
renderDevTool() {
// 在开发环境下,添加调试工具;
if (process.env.NODE_ENV !== 'production') {
const DevTools = require('mobx-react-devtools').default;
return <DevTools />;
}
}
尽可能多地使用小组件
@observer 组件会追踪render方法中所有的可观测的值,当任何一个值变化的时候,都会重新渲染,所以组件越小,重新渲染的变化就越小。
在专用的组件中渲染列表
/**
* TodoMain.jsx
**/
<div>
<ul>{todoList.map((todo) => {
return <li className="todo-item" key={todo.id}>
<TodoItem todo={todo} />
<span className="delete" onClick={e => todoStore.removeTodo(todo)}>X</span>
</li>
})}</ul>
</div>
/**
* TodoItem.jsx
**/
import React, { Component, Fragment } from 'react';
import { observer } from 'mobx-react';
@observer
class TodoItem extends Component {
handleCeckboxClick = () => {
this.props.todo.toggle();
}
render () {
const { todo } = this.props;
return (
<Fragment>
<input
type="checkbox"
className="toggle"
checked={todo.finished}
onClick={this.handleCeckboxClick}
/>
<span className={['title', todo.finished && 'finished'].join(' ')}>{todo.title}</span>
</Fragment>
)
}
}
export default TodoItem;
晚一点使用间接引用值
使用 mobx-react 时,推荐尽可能晚的使用间接引用值。 这是因为当使用 observable 间接引用值时 MobX 会自动重新渲染组件。 如果间接引用值发生在组件树的层级越深,那么需要重新渲染的组件就越少
快的: <DisplayName person={person} />
慢的: <DisplayName name={person.name} />
store
,通过拆分reducer
来拆分应用逻辑,单一store
可以方便不同组件之间进行的数据共享;store
,把逻辑拆分到不同的store
中,当维护多个组件之间的数据共享、相互之间的引用的时候会变得特别的麻烦。state
不可改变,每次状态的变化,都会创建一个新的state
state
是可观测对象,并且state
可以被直接的修改,state
的变化会自动触发使用它的组件重新渲染。@observer
修饰组件就可在修改数据的时候自动进行处理更新,同时免面向对象的编程的写法感觉如果熟悉面向对象编程的开发人员,会减少学习的成本;store
对于数据的管理,更加容易的跟踪state
,但是学些成本和负责的更改数据流程让人觉得不是很友好。A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.