WRY

Where Are You?
You are on the brave land,
To experience, to remember...

0%

React 入门尝试

《 React 快速上手开发》

第一章 Hello World!

一个原生的简单的React应用只需要包含如下几部分

  • 引入React库及其DOM插件库,通过<script src> 的方式

  • 定义React应用应该出现在页面上的位置<div id="app">

  • 使用React输出组件中的内容

完整代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html>
<head>
<title>hello React </title>
<meta charset="utf-8">
</head>
<body>
<div id="app">
<!-- 定义要渲染的位置 -->
</div>
<!-- 引入React和DOM插件 -->
<script src="react/build/react.js"></script>
<!-- 可以选择addons代替react.js,使用到React更多的功能函数 -->
<!-- <script src="react/build/react-with-addons.js"></script> -->
<script src="react/build/react-dom.js"></script>
<script>
// 使用React进行内容渲染
ReactDOM.render(
React.DOM.h1(null, "Hello world!"), // 渲染的内容
document.getElementById("app") // 渲染的位置
);
</script>
</body>
</html>

其中的React.DOM是一个预定义好的HTML元素集合,例如React.DOM.h1() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
React.DOM.h1(
{
id: "my-heading",
// 下面几个属性分别对应原生的class,for和style,在style中注意需要将font-family改写成fontFamily,因为这些命名在js中是保留字或者非法。
className: "pretty",
htmlFor: "me",
style: {
background: "black",
color: "white",
fontFamily: "Verdana",
}
}, // 第一个参数是为组件定义任何属性
React.DOM.span(null,
React.DOM.em(null, "Hell"),
"o"
), // 第二个和之后更多的参数,可以进行组件内子元素的组合和嵌套
" world!"
)

上述方法中描述的内容都是可以通过JSX进行更方便的书写,但需要明白那些更方便的书写的本质是React.DOM.*,而React.DOM.*是对React.createElement()的封装,下面的代码等价于上面的写法

1
2
3
4
ReactDOM.render(
React.createElement("span", null, "Hello"),
document.getElementById("app")
);

第二章 组件的生命周期

定义一个组件,组件中包含的数据句柄有this.propsthis.state两个,这两个变量都应认为他们是只读的,第一个props更类似于静态的构建,而第二个state更类似于动态的构建,可以通过调用this.setState()方法更改数值并触发React重新render绘制内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
var logMixin = {
_log: function(methodName, args) {
console.log(this.name + '::' + methodName, args);
},
componentWillUpdate: function() {this._log('componentWillUpdate', arguments);},
componentDidUpdate: function() {this._log('componentDidUpdate', arguments);},
componentWillMount: function() {this._log('componentWillMount', arguments);},
componentDidMount: function() {this._log('componentDidMount', arguments);},
componentWillUnmount: function() {this._log('componentWillUnmount', arguments);},
};

var Component = React.createClass({
name: 'componentName', // 给组件定义名称,方便进行位置定位
// 引入logMixin定义的内容,可以十分方便的实现代码复用
mixins: [logMixin],
// 可以通过propTypes定义一个组件被使用时对传入参数的需要,这是一个很好的习惯,减少使用时查找需要的属性和属性的类别
propTypes: {
// 需要注意的是此处可以不罗列出所有的属性
name: React.PropTypes.string.isRequired,
text: React.PropTypes.string,
},
// 可以通过实现getDefaultProps函数的方式,为可选属性提供默认值
getDefaultProps: function () {
return {
text: 'ReactLearn',
};
},
// 通过实现getInitialState函数保证可以通过this.state变量合法的取到数据
getInitialState: function () {
return {
text: this.props.text,
};
},
// 用户定义函数,更新state变量的内容
_textChange: function (ev) {
this.setState({
text: ev.target.value,
});
},

// 组件的生命周期函数
// 组件再次渲染之前
componentWillUpdate: function() {this._log('componentWillUpdate', arguments);},
// 组件再次渲染之后
componentDidUpdate: function() {this._log('componentDidUpdate', arguments);},
// 组件第一次渲染之前
componentWillMount: function() {this._log('componentWillMount', arguments);},
// 组件第一次渲染之后,可以在这个环节检查数据是否合法,不合法刷新回原来的状态
componentDidMount: function() {this._log('componentDidMount', arguments);},
// 组件被删除之前
componentWillUnmount: function() {this._log('componentWillUnmount', arguments);},

render: function () { // 唯一必须要做的事情就是实现render方法
// 可以通过this.props属性获取传入的参数,需要注意的是应该将this.props看作是只读的
return React.DOM.div(null,
React.DOM.span(null, "My name is " + this.props.name),
React.DOM.textarea({
value: this.state.text,
onChange: this._textChange,
}),
React.DOM.h3(null, this.state.text.length)
);
}
});

使用一个组件

1
2
3
4
5
6
7
8
9
10
ReactDOM.render(
React.createElement(Component, {name: "Bob"}), // 向子组件中传入参数
document.getElementById("app")
);
// 也可以使用工厂模式,生成多个实例
var ComponentFactory = React.createFactory(Component);
ReactDOM.render(
ComponentFactory(),
document.getElementById("app")
);

第三章 Excel: 一个出色的表格组件

一个非常简短但功能完整的例子,这里简单记录一下阅读过程中感觉比较重要的几个点:

  • 更新数据的时候,需要先将需要更新的数据拷贝出来,更新之后,然后进行再调用setState()函数更新
  • 如何在表格中定位和编辑数据
  • 不要担心大量前端代码的重复render(),React会使用DOM diff算法,实现最小范围的重新渲染

第四章 JSX

JSX是一项完全独立于React的技术,但又是完全兼容的,可以简化React.DOM.*的繁琐书写。

转译JSX

比较原始的在客户端中转义jsx,可以引入babel/browser.js,然后对需要转义的js代码进行标识type="text/babel",让Babel在浏览器执行这些代码的时候进行转义。

1
2
<script src="babel/browser.js"></script>
<script type="text/babel">...</script>

在JSX 中使用JavaScript

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
render: function() {
var state = this.state;
return (
<table>
<thead onClick={this._sort}>
<tr>{
this.props.headers.map(function(title, idx) {
if (state.sortby === idx) {
title += state.descending ? ' \u2191' : ' \u2193'
}
return <th key={idx}>{title}</th>;
})
}</tr>
</thead>
<tbody>
{
this.state.data.map(function(row, idx) {
return (
<tr key={idx}>{
row.map(function(cell, idx) {
return <td key={idx}>{cell}</td>;
})
}</tr>
);
})
}
</tbody>
</table>
);
}

在JSX中使用空格

JSX和HTML有很多相同之处,但是也有自己的特殊语法,简化很多操作

1
2
3
4
5
6
7
8
ReactDOM.render(
<h1>
{1}
{' plus '}
{2}
</h1>,
document.getElementById('app')
);

在JSX中使用HTML实体

需要注意编码的问题

展开属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var attr = {
href: 'http://example.org',
target: '_blank',
};
// 比较原始的方法
return (
<a
href={attr.href}
target={attr.target}>
Hello
</a>
);
// 使用展开属性
return <a {...attr}>Hello</a>;

JSX 比较完整的demo

累了,下次补上。

React 动手尝试教程

基础命令记录

1
2
3
4
5
$ npx create-react-app {projectName} # 创建一个项目
$ cd react-tutorial && npm start # 启动一个测试下的服务

# 常用组件安装
$ npm install moment # 一个好用的时间组件

配套JS组件

moment 教程

配套前端UI

布局组件

Plex 教程

容器的属性 可选值 含义
flex-direction row | row-reverse | column | column-reverse 排列方向
flex-wrap nowrap | wrap | wrap-reverse 换行策略
flex-flow flex-direction属性和flex-wrap属性的简写形式
justify-content flex-start | flex-end | center | space-between | space-around 项目在主轴上的对齐方式
align-items flex-start | flex-end | center | baseline | stretch 项目在交叉轴上的对齐方式
align-content flex-start | flex-end | center | space-between | space-around | stretch 多根轴线的对齐方式

路由组件

umi

图表组件

antV

ES6 学习

let 和 const命令

声明变量的6种方式

  • var
  • function
  • let
  • const
  • import
  • class

var, let 和 const 关键字

var声明的变量

  • 在全局范围内有效

  • var定义的变量可以先用后定义,先用的时候为状态(变量提升**)。

1
2
3
4
5
6
7
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i); // 其中的i指向的是全局的i,这个i是随着循环一直在变化的
};
}
a[6](); // 10

let声明的变量

  • 只在它所在的代码块中生效
  • let定义的变量,必须先定义然后才能用,否则会出错(不存在变量提升)。
  • 不允许在相同的作用域内重复声明相同的变量
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
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i); // ReferenceError: i is not defined

var a = [];
for (let i = 0; i < 10; i++) { // 每轮循环的i都是一个新的变量,只在本轮循环中有效
a[i] = function () {
console.log(i);
};
}
a[6](); // 6

// 不允许在相同的作用域内重复声明相同的变量
function func(arg) {
let arg;
}
func() // 报错

function func(arg) {
{
let arg; // 不在同一个块内
}
}
func() // 不报错

const声明的变量

  • 是一个只读的常量,一旦读出来就不能再更改
  • 声明的变量必须立即初始化,不能留到以后的赋值
  • 本质是变量所指向的内存地址上保存的数据不能改变

作用域

在ES6中增加了块级作用域,可以

  • 防止内层变量覆盖外层变量
  • 防止局部变量泄漏成全局变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 在ES5中
var tmp = new Date();

function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world'; // ES5中 由于变量提升的效果,会造成tmp变成undefined的状态
}
}

f(); // ES5中 undefined

// 在ES5中
var s = 'hello';

for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}

console.log(i); // 5 块内变量泄漏成了全局变量
1
2
3
4
5
6
7
8
// 在ES6中,外层代码块不受内层代码块的影响
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}

只要块区中存在letconst定义的变量,就会形成封闭作用域(被称为暂时性死区),在此之间声明的变量都会出错,即使在全局中已经有了该变量的声明

1
2
3
4
5
6
7
// 第一个例子
var tmp = 123;

if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
1
2
3
// 另一个例子
typeof x; // ReferenceError
let x;
1
2
3
4
5
6
7
8
9
10
function bar(x = y, y = 2) { // x = y, 但是y还没有定义,属于死区,就会报错
return [x, y];
}

bar(); // 报错

function bar(x = 2, y = x) { // 此种方法就不存在死区了
return [x, y];
}
bar(); // [2, 2]

for循环的作用域说明

1
2
3
4
5
6
7
for (let i = 0; i < 3; i++) {
let i = 'abc'; // 函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域
console.log(i);
}
// abc
// abc
// abc

顶层对象属性

在浏览器环境中指的是window对象,在Node中指的是global。顶层对象的属性和全局变量是等价的。

1
2
3
4
5
window.a = 1;
a // 1

a = 2;
window.a // 2

var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面的规定,let命令、const命令、class命令声明的全局变量不属于顶层对象的属性。

  • 全局环境中,this会返回顶层对象。但是,Node.js 模块中this返回的是当前模块,ES6 模块中this返回的是undefined
  • 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined
  • 不管是严格模式,还是普通模式,new Function('return this')(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么evalnew Function这些方法都可能无法使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 可以在所有情况下,都尽量取到顶层对象
// 方法一
(typeof window !== 'undefined'
? window
: (typeof process === 'object' &&
typeof require === 'function' &&
typeof global === 'object')
? global
: this);

// 方法二
var getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};

变量的解构赋值

数组的解构赋值

基于模式匹配的思想,将等号右边的数组中对应的值赋给等号左边等价位置上的变量,如果解构不成功的话,对应值会等于undefined。

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
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

// 部分结构
let [x, y] = [1, 2, 3];
x // 1
y // 2

let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4

只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。

1
2
3
4
5
6
7
8
9
10
11
function* fibs() {
let a = 0;
let b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}

let [first, second, third, fourth, fifth, sixth] = fibs();
sixth // 5

解构允许指定默认值,需要注意的是 null != undefined

1
2
3
4
5
let [foo = true] = [];
foo // true

let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'

对象的解构赋值

对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

1
2
3
4
5
6
let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined

对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。

1
2
3
4
5
6
// 例一
let { log, sin, cos } = Math;

// 例二
const { log } = console;
log('hello') // hello

若想让变量名和属性名称不一样,则可以写成下面这样

1
2
3
4
5
6
7
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"

let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj; // 使用first寻找值,赋给冒号后面的变量
f // 'hello'
l // 'world'

解构同样也适用于嵌套结构的对象

1
2
3
4
5
6
7
8
9
10
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};

let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"

嵌套结构目标内容到变量的嵌套解构中

1
2
3
4
5
6
7
let obj = {};
let arr = [];

({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true });

obj // {prop:123}
arr // [true]

字符串扩展

字符串的for...of遍历

1
2
3
for (let i of text) {
console.log(i);
}

困了困了,以后继续看