近期在学习IBM全栈应用开发微学士课程,故此记录学习笔记。

1. 使用React和ES6创建前端应用

1.1. 前端框架

前端框架用于创建可连接服务器的动态客户端应用程序。它们通常是开源项目:

  • Angular
  • React
  • Vue

1.1.1. Angular

Angular是一个开源的框架,由谷歌维护。它基于HTML和JavaScript,并且易于实现。

Angular使用指令来使HTML更加动态,所有指令都可用于包含库的HTML。

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>

<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.16/angular"></script>
</head>
<body ng-app>
Company Name: <input type="text" ng-model="comp_name">
<br>
<label ng-bind="comp_name"></label>
</body>
</html>

1.1.2. Vue

Vue是一个开源的前端框架,它使用虚拟DOM来实现高性能,HTML被视为一个完整的对象。Vue非常轻量级、渲染速度快、易于学习。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<html>
<head>
<title>VueJS Introduction</title>
<script src="https://cdn.jsdelivr.net/.../vue.js"></script>
</head>
<body>
<div id="intro" style="text-align: center;">
<h5>
{{ message }}
</h5>
</div>
<script type="text/javascript">
var vue_det = new Vue({
el: "#intro",
data: {
message: "This is a Vue HTML"
}
});
</script>
</body>
</html>

1.1.3. React

React是一个用于构建客户端动态网络应用程序的框架,使用动态数据绑定和虚拟DOM来扩展HTML语法,而不需要编写额外的代码,并保持用户界面元素与应用程序状态的同步。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<body>
<h1>
Watson Author Finder
</h1>
<p>
Please write your details
</p>
<form>
<input type="text" id="name">
<input type="text" id="age">
</form>
</body>
</html>

React使用JavaScript XML这种类似于HTML的特殊语言来创建用户界面,其可被Babel编译器编译为JavaScript。

JavaScript XML要嵌入在特殊的脚本标签中,其中的type属性指定了需要Babel的内容。

用于构建React应用程序的三个重要软件包:

  • React包:保存组件以及其状态和属性的React源代码
  • ReactDOM包:React和DOM之间的粘合剂
  • Babel编译器:将JavaScript XML编译为JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<html>
<!-- Load React API -->
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<!-- Load React DOM -->
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<!-- Load Babel Compiler -->
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>

<body>
<div id="comp1"></div>
<script type="text/babel">
class Mycomp extends React.Component {
// override the render method
render() {
return <h1>This is my own component named {this.props.name}</h1>;
}
}
ReactDOM.render(<Mycomp name="myBrandNewComp")/>, document.getElementById("comp1"));
</script>
</body>
</html>
  • React组件要在<script>标签中定义,type属性的类型需要设置为text/babel,以便Babel编译器将其编译为JavaScript
    • 定义的组件为Mycomp,继承自React.Component,并重写了render()方法
  • ReactDOM.render()方法用于渲染组件,并指定组件名称、HTML标签和要设置的任何属性(该例子中就设置了name属性)
  • 组件需要被指定呈现在HTML页面的哪个位置(该例子中就是comp1

Facebook提供了一个名为“Create React App”的工具,可以简化创建React应用程序的过程。

如果已安装Node.js,就可以运行以下命令来安装Create React App:

1
npx create-react-app my-app

当运行完上述命令后,系统会自动创建一个包含所有必要文件的目录结构。该目录结构包含创建和运行React应用程序所需的所有文件。

  • src目录是我们需要修改的主要目录
    • App.js文件是我们要添加到HTML页面的React根组件
    • index.js文件将应用程序添加到HTML页面

1.2. ES6

ES6的全程为ECMAScript 6,制定了广泛的全球信息和通信技术标准。

JavaScript遵循ECMAScript 6标准(2015年),其最主要的更改是:

  • let
  • const
  • 箭头函数
  • Promise构造函数

1.2.1. letconst

let/constvar不同:

  • var声明的变量的作用域是全局的。这很有挑战性,尤其是在大型项目中,代表着有许多变量需要维护
  • let可以将变量的作用域限制在声明变量的代码块中
    1
    2
    3
    4
    5
    function() {
    let num = 5;
    num = 6;
    }
    console.log(num); // will throw an error
  • const声明的变量的值不能被修改
    1
    2
    3
    4
    const num = 5;
    console.log(num);
    num = 6; // will throw an error
    console.log(num);

1.2.2. 箭头函数

箭头函数允许函数像变量一样声明,这是一种更简洁的函数声明方式。

1
2
3
4
5
6
7
// how a function was written in the older ES5 JavaScript
function sayHello() {
console.log("ES5 function - Hello world!");
}

// arrow function in ES6
const sayHello = ()=> console.log("ES6 function - Hello world!");

箭头函数可以被调用,并可以作为回调的参数传递。

1
2
const sayHello = ()=> console.log("Hello world!");
setTimeout(sayHello, 1000);

箭头函数也可以像普通函数一样接受参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// takes one parameter
// brackets are not mandatory
// because the code returns a value, it must be in curly brackets
const oneParamArrowFunc = name => {return "hello " + name};

// function brackets must be put around the parameters list
// does not need curly brackets because it only has one line of code and returns nothing
const twoParamsArrowFuncWithoutReturn = (first, last) => console.log("hello " + first + " " + last);

const twoParamsArrowFuncWithReturn = (first, last) => {return "hello " + first + " " + last};

const twoParamsTwoLinesArrowFunc = (first, last) => {
const greeting = "hello ";
return greeting + " " + first + " " + last;
}

1.2.3. Promise

Promise对象表示了一个异步操作的最终完成或失败,以及其返回值。每当你调用异步操作时,Promise会处于pending(挂起)状态;当操作成功地执行时,Promise会处于fulfilled(履行)状态;当操作失败时,Promise会处于rejected(拒绝)状态。

1
2
3
4
5
6
7
8
9
10
11
12
let promiseArgument = (resolve, reject) => {
setTimeout(() => {
let currTime = new Date().getTime();
if (currTime % 2 === 0) {
resolve("Success!");
} else {
reject("Failed!");
}
}, 2000);
};

let myPromise = new Promise(promiseArgument);
1
2
3
4
5
6
7
8
9
10
let myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
let currTime = new Date().getTime();
if (currTime % 2 === 0) {
resolve("Success!");
} else {
reject("Failed!");
}
}, 2000);
});

以上两种写法是等价的。

1.2.4. 类

ES6中的类使面向对象编程在JavaScript中更加容易。类创建了对象的模板,且建立在原型(即prototype,是所有JavaScript对象的属性,包括函数,而函数可用于创建对象实例)的基础上。

1
2
3
4
5
6
7
function Person(name, age) {
this.name = name;
this.age = age;
return this;
}

let person1 = Person("John", 20);
  • this指代的是当前对象
  • 类的概念是在函数原型的前提下建立的,目的是将面向对象编程扩展到JavaScript中

构造函数(constructor)是一个特殊的函数,用于创建一个类对象:

1
2
3
4
5
6
7
8
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
};

let myRectangle = new Rectangle(10, 5);
  • 使用new关键字就可以创建一个类的实例

在JavaScript ES6中,类可以继承自其他类。继承其他类的类被称为子类(subclass),而超类(superclass)是被子类继承的类。子类会继承超类的所有属性和方法。

子类具有特殊权限,能够使用super()方法来调用超类的构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Square extends Rectangle {
constructor(height, width) {
if (height === width) {
super(height, width);
} else {
// if the height is not the same as the width specified,
// the width will become equal to the height
super(height, height);
}
}
}

let mySquare = new Square(5, 5);

1.3. JSX

JSX是JavaScript XML或JavaScript Syntax Extension的缩写,是一种类似于React使用的XML或HTML类语法,用于创建React元素。 JSX允许XML或HTML类文本与JavaScript或React代码并存。

JSX使用预处理器将JavaScript文件中的HTML类文本转换为标准的JavaScript对象,例如转译器或编译器(比方说Babel)。

1
const el1 = <h1>This is a sample JSX code snippet</h1>
  • JSX代码的语法就像是HTML使用了类似JavaScript的变量

1.3.1. React代码例子

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from 'react'

function App () {
return (
<div>
<p>This is a sample list</p>
<ul>
<li>List item no. 1</li>
<li>List item no. 2</li>
</ul>
</div>
);
}

而这是普通的JavaScript代码:

1
2
3
4
5
6
7
8
9
10
11
12
import React from 'react'

function App () {
return React.createElement(
"div",
null,
React.createElement("p", null, "This is a sample list"),
React.createElement("ul", null,
React.createElement("li", null, "List item no. 1"),
React.createElement("li", null, "List item no. 2"))
);
}

可以看出来,如果没有JSX,React代码将不得不使用大量嵌套来编写,这会导致代码变得难以阅读和维护。

1.3.2. 组件

组件(component)是React的核心构件,是一个可重用的代码块,用于创建用户界面。组件可以是函数或类,它们接受输入并返回React元素。

组件可以拥有自己的状态,这些状态是描述了组件行为的对象。有状态的组件的类型为类,而无状态的组件的类型为函数。

React组件通过三个概念实现这些功能:

  1. 属性(property):用于从父组件向子组件传递数据
  2. 事件(event):使组件能够管理DOM事件和用户在系统上交互的动作
  3. 状态(state):根据组件的当前状态更新用户界面

React应用程序是一颗组件树:根组件就像一个容器,它包含了所有其他组件。 所有组件的名称,无论是函数还是类,都必须以大写字母开头。组件可以通过使用className属性和CSS来进行样式化。

组件类型:

  1. 函数式组件通过编写JavaScript函数来创建,可以接受也可以不接受数据作为参数,返回JSX函数。它们本身没有状态或生命周期方法,因此也被称为无状态组件,但是可以通过实现React Hooks来添加这些功能。

    • React Hook是React的一项新功能,它能让你在不编写类的情况下使用React的特性
    • 生命周期方法(lifecycle methods)是React内置的方法,可以在DOM中的整个持续时间内对组件进行操作

    函数式组件用于显示易于阅读、调试和测试的静态数据。

    1
    2
    3
    const Democomponent = () => {
    return <h1>welcome Message!</h1>;
    }

    当组件有属性但生命周期不需要管理时最有用。

    函数式组件可以接受用户自定义的属性作为参数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function App(props) {  // props passed as a function parameter
    const compStyle = {
    color: props.color,
    fontSize: props.size + 'px'
    };

    return (
    <div>
    <span style={compStyle}>I am a sentence.</span>
    </div>
    );
    }

    export default App;
    1
    2
    3
    4
    5
    6
    ReactDOM.render(
    <React.StrictMode>
    <App color="blue" size="25"/> <!-- props being sent to the component -->
    </React.StrictMode>,
    document.getElementById('root')
    );

    事件处理程序(event handler)可以通过属性来设置,其中onClick处理程序在功能组件中使用的最多:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import React from 'react';
    import ReactDOM from 'react-dom';
    import App from './App';

    ReactDOM.render(
    <React.StrictMode>
    <App color="blue" size="25" clickEvent={ <!-- setting an event handler method as a property -->
    () => { alert("You clicked me!") }
    }/>
    </React.StrictMode>,
    document.getElementById('root')
    );
    1
    2
    3
    4
    5
    6
    7
    8
    9
    function App(props) {
    return (
    <div>
    <button onClick={props.clickEvent}>Click Me!</button> <!-- setting an event handler from props -->
    </div>
    );
    }

    export default App;
  2. 类组件要比函数式组件更复杂,它们可以将数据传递给其他类组件、可以被JavaScript ES6的类创建、可以使用状态、属性和生命周期方法等React功能。

    1
    2
    3
    4
    5
    class Democomponent extends React.Component {
    render() {
    return <h1>Welcome Message!</h1>;
    }
    }

    由于其多功能性,类组件要比函数式组件更受青睐。由于它们继承了React.Component,因此必须要覆盖render()方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // import the React module from the react package
    import React from 'react';

    // create the App class that extends React.Component
    class App extends React.Component {
    constructor(props) {
    super(props)
    }

    // override the render method
    render() {
    return <button onClick={this.props.clickEvent}>Click Me!</button>;
    }
    }

    export default App;

    props在类组件外部设置,而状态要在类组件内部设置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import Reach from 'react';

    class App extends React.Component {
    constructor(props) {
    super(props)
    }
    state = {counter: "0"}; // define the state counter of the component App

    // a function to increment the counter every time a button is clicked
    incrementCounter = () => {
    this.setState({counter: parseInt(this.state.counter) + 1});
    }

    // override the render method
    render() {
    return <div>
    <button onClick={this.incrementCounter}>Click Me!</button>
    <br/>
    {this.state.counter}
    </div>
    }
    }
  3. 纯组件(pure component)优于函数式组件,主要用于提供优化。它们是编写起来最简单最快的组件,不依赖于其作用域之外的任何变量状态,可以用来替代简单的函数式组件。

  4. 高阶组件(higher-order component)是React中重用组件逻辑的高级技术。API不提供高阶组件。它们返回组件的函数,用于与其他组件共享逻辑。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // import React and React Native's Text Core Component
    import React from 'react';
    import { Text } from 'react-native';

    // define a component as a function
    const Helloworld = () => {
    return (
    <Text>Hello, World!</Text>
    );
    }

    // export your function component
    // the function can then be imported in any application
    export default Helloworld;

2. React组件

2.1. 状态

状态允许你在一个应用程序中修改数据。它被定义为一个对象,使用键值对来存储数据,并帮助你跟踪应用程序中不同类型的数据。

React组件有一个内置的状态对象,可以在状态对象中存储属于组件的属性值。当状态对象发生变化时,组件会重新渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// component
class TestComponent extends React.component {
constructor() {
this.state = {
id: 1,
name: "John",
age: 28
};
}
render() {
return (
<div>
<p>{ this.state.name }</p>
<p>{ this.state.age }</p>
</div>
)
}
}
  • 本代码示例展示出了如何创建一个测试组件,该组件包含idnameage三个状态属性
  • 组件的render()方法返回了状态属性的值
  • 包含属性的状态将根据组件的要求进行更改

React状态的类型:

  1. 共享状态(shared state):由多个组件共享,比较复杂。例如订单应用程序中的所有订单列表
  2. 本地状态(local state):存在于单个组件中,不用于其他组件。例如隐藏和显示信息

2.2. 属性

属性用于在React组件之间传递数据。工作方式与HTML属性类似,它们存储标签的属性值。

React组件之间的数据流是从父组件到子组件的单向数据流。

属性可以像函数参数一样被传递,但它们是只读的,不能在组件内部更改。属性允许子组件访问父组件中被定义的方法(状态则是由父组件管理,而子组件没有自己的状态),大部分组件将根据接收到的属性来显示信息,并保持无状态。

1
2
3
4
5
6
7
8
9
10
// component
class TestComponent extends React.component {
render() {
return <div>Hi { this.props.name }</div>
}
}

// passing the props as examples to the text component
<TestComponent name="John" />
<TestComponent name="Jill" />
  • 该代码示例创建了一个类TestComponent,该类扩展了React组件

2.3. 组件阶段

每个React组件在其生命周期中都有三个阶段:

  1. 挂载阶段(mounting phase):组件被创建并插入DOM中。当组件被创建时,会有四个方法被依次调用:

    1. constructor():用于初始化组件的状态和属性
    2. getDerivedStateFromProps():用于更新组件的状态
    3. render():用于渲染组件;必须且只能返回一个DOM元素
    4. componentDidMount():用于在组件被插入DOM后执行一些操作
    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
    import React from 'react';

    class App extends React.Component {
    // when the component App is created, the constructor is invoked
    constructor(props) {
    super(props)
    console.log("Inside the constructor")
    }

    // the componentDidMount method is invoked
    componentDidMount = () => {
    console.log("Inside component did mount")
    }

    // the render method is invoked
    render() {
    console.log("Inside render method")
    return (
    <div>
    The component is rendered
    </div>
    );
    }
    }

    export default App;
  2. 更新阶段(updating phase):组件的状态或属性发生变化时,会触发更新阶段。当组件更新时,会有五个方法被依次调用:

    1. getDerivedStateFromProps():用于更新组件的状态
    2. shouldComponentUpdate():每当状态发生变化时被调用;默认返回true;应当仅在不想渲染状态的变化时返回false
    3. render():用于渲染组件;必须且只能返回一个DOM元素
    4. getSnapshotBeforeUpdate():用于在DOM更新前获取DOM状态
    5. componentDidUpdate():用于在DOM更新后执行一些操作
    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
    import React from 'react';

    class App extends React.Component {
    state = {counter: "0"};

    incrementCounter = () => this.setState({counter: parseInt(this.state.counter) + 1});

    // returns true by default
    // its behavior is rarely changed
    shouldComponentUpdate() {
    console.log('Inside shouldComponentUpdate')
    return true;
    }

    getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log('Inside getSnapshotBeforeUpdate');
    console.log('Prev counter is ' + prevState.counter);
    console.log('New counter is ' + this.state.counter);
    return prevState;
    }

    componentDidUpdate() {
    console.log('Inside componentDidUpdate')
    }

    // logs on to the console and then renders the component
    render() {
    console.log('Inside render')
    return (
    <div>
    <!-- With the onClick of the button, incrementCounter is invoked, increasing the counter state by 1 -->
    <button onClick={this.incrementCounter}>Click Me!</button>
    {this.state.counter}
    </div>
    );
    }
    }

    export default App;
  3. 卸载阶段(unmounting phase):组件从DOM中移除时,会触发卸载阶段。当组件被卸载时,会有一个方法被调用:

    1. componentWillUnmount():用于在组件被卸载前执行一些操作
    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
    import React from 'react';

    class AppInner extends React.Component {
    componentWillUnmount() {
    console.log('This component will unmount')
    }

    render() {
    return <div>Inner component</div>
    }
    }

    class App extends React.Component {
    state = {innerComponent:<AppInner/>}

    componentDidMount() {
    setTimeout(() => {
    this.setState({innerComponent: <div>unmounted</div>})
    }, 5000)
    }

    render() {
    console.log('Inside render')
    return (
    <div>
    {this.state.innerComponent}
    </div>
    );
    }
    }

    export default App;

2.4. 组件之间的数据传递

React组件之间的数据传递可以有:

  1. 使用属性的“父到子”数据传递
  2. 使用回调函数的“子到父”数据传递
  3. 使用Redux的“兄弟”数据传递(此处不做讨论)

父到子:

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
class App extends React.Component {
state = {childColor: "green", name: "John"}

changeColor = () => {
const newcolor = document.getElementById('colorbox').value;
this.setState({childColor: newcolor})
}

changeName = () => {
const newname = document.getElementById('namebox').value;
this.setState({name: newname})
}

render() {
console.log('Inside render')
return (
<div>
Color <input type="Text" onChange={this.changeColor} id="colorbox" />
<br/>
Name <input type="Text" onChange={this.changeName} id="namebox" />

<!-- App sets the property color and name for AppInner -->
<!-- The data is passed to the child every time a new value is entered in the input boxes in the parent -->
<AppInner color={this.state.childColor} name={this.state.name} />
</div>
);
}
}
1
2
3
4
5
6
7
8
9
10
class AppInner extends React.Component {
constructor(props) {
super(props)
}

render() {
const txtStyle = {color: this.props.color}
return <span style={txtStyle}>{this.props.name}</span>
}
}
  • 其中,App组件是AppInner组件的父组件

子到父:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class App extends React.Component {
state = {message: ""}

// Func1 is a parent component function which takes a string argument
func1 = (childData) => {
this.setState({message: childData})
}

render() {
return (
<div>
<!-- Pass the callback func1 as a property to the child -->
<AppInner parentCallback = {this.func1} />
<p>{this.state.message}</p>
</div>
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class AppInner extends React.Component {
sendData = () => {
setInterval(() => {
const currTime = Date();
// the parent class method which sets the state of the parent component
this.props.parentCallback(currTime);
}, 1000);
}

// invoke the sendData method
componentDidMount() {
this.sendData();
}

render() {
return <div></div>
}
}

2.5. 组件的生命周期

组件的生命周期代表了组件从创建到销毁的整个过程。React组件的生命周期包含四个阶段,每个阶段都有不同的方法:

  1. 初始化(initialization):组件以给定的属性和默认状态被创建
  2. 挂载(mounting):渲染由render()方法返回的JSX
  3. 更新(updating):当组件的状态或属性发生变化时,会触发更新阶段
  4. 卸载(unmounting):组件从DOM中移除

2.5.1. 挂载阶段

挂载阶段中,组件被添加到DOM,并在组件加载前和加载后调用两个预定义方法:

  1. componentWillMount()
  2. componentDidMount()

2.5.2. 更新阶段

组件的状态或属性发生变化时,会触发更新阶段。变化可以在组件内发生,也可以通过后台发生,这些变化都会触发render()方法的调用。

  1. getDerivedStateFromProps()
  2. shouldComponentUpdate()
  3. render()
  4. getSnapshotBeforeUpdate()
  5. componentDidUpdate()

2.5.3. 卸载阶段

组件从DOM中移除时,会触发卸载阶段。在卸载阶段,只有一个方法被调用:

  1. componentWillUnmount()

2.6. 外部服务

路由器(router)可以连接到外部服务以执行多种操作,例如:

  1. GET:从服务器获取数据

    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
    class App extends React.Component {
    state = {
    user: "None Logged In"
    }

    // connect to a server through an axios request
    componentDidMount() {
    const req = axios.get("<external server>");
    req.then(resp => {
    // then the promise is fulfilled, you parse the response and extract the data from it to change user to have the same name as its value
    this.setState({user: resp.data.name});
    })
    .catch(err => {
    this.setState({user: "Invalid user"});
    });
    }

    render() {
    return (
    <div>
    Current user - {this.state.user}
    </div>
    );
    }
    }
  2. POST:将数据发送到服务器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    const express = require("express");
    const app = new express();

    // this server uses the CORS middleware to allow cross-origin requests to the server
    const cors_app = require("cors");
    app.use(cors_app());

    let usercollection = [];

    app.post("/user", (req, res) => {
    let newuser = {"name": req.query.name, "gender": req.query.gender}
    usercollection.push(newuser);
    return res.send("User successfully added");
    });

    app.get("/user", (req, res) => {
    return res.send(usercollection);
    })

    app.listen(3333, () => {
    console.log("Listening at http://localhost:3333")
    })
    • Express服务器接收端点/userPOST请求,并将数据存储在usercollection数组中
    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
    class App extends React.Component {
    state = {completionstatus: ""}

    postDataToServer = () => {
    axios.post("http://localhost:3333/user?name=" +
    document.getElementById("name").value +
    "&gender=" + document.getElementById("gender").value
    )
    .then(response => {
    this.setState({completionstatus: response.data})
    }).catch((err) => {
    this.setState({completionstatus: "Operation failure"})
    })
    }

    render() {
    return (
    <div>
    Enter the name <input type="text" id="name" />
    <br />
    Enter the gender <input type="text" id="gender" />
    <br />
    <button onClick={this.postDataToServer}>Post Data</button>
    <span>{this.state.completionstatus}</span>
    </div>
    );
    }
    }
  3. UPDATE:修改数据

  4. DELETE:删除数据

大多数对外部服务器的请求都是阻塞性的。要异步调用,可以使用Promise。

2.7. 测试

测试可以是一套由代码组成的,以验证应用程序的无差错执行。

测试React组件有多个好处:验证代码运行无误;通过复制最终用户的行为来测试组件;通过测试组件的不同状态来测试组件;防止先前已修复的错误再次出现。

测试有着两种类型:

  1. 在简单的测试环境中渲染组件树并验证其输出
  2. 在真实的浏览器环境中运行应用程序,进行端到端的测试

2.7.1. React组件测试的阶段

  1. 安排(arrange):组件需要将其DOM渲染到用户界面
  2. 操作(act):注册任何可能以编程方法触发的用户行为
  3. 断言(assert):验证组件的输出是否与预期的输出相匹配

2.7.2. 测试工具

速度vs环境:

  • 有些工具能在做出修改和看到结果之间提供非常快的回馈,但无法精确地模拟浏览器行为
  • 有些工具可能会使用真实的浏览器环境,但会降低迭代速度,在持续集成环境中使用时可能会导致不稳定

测试工具有:

  • Mocha
  • Chai:断言库
  • Sinon
  • Enzyme:渲染组件
  • Jest:测试React组件,并拥有着Mocha、Chai、Sinon以及其他工具的能力
  • React Testing Library:测试React组件

3. React进阶

3.1. Hooks

Hooks是在用户界面中封装有状态的行为的更简单的方法,它们允许函数式组件访问状态和其他React功能。Hooks是常规的JavaScript函数,提供使用上下文或状态等功能的方法,且无需编写类,帮助你使代码更简洁。

类组件有时会带来一些问题,例如封装复杂、组件大小难以管理以及类混淆等。

标准的Hooks:

  • useState:为函数式组件添加状态
  • useEffect:管理副作用(side effects)
  • useContext:管理上下文
  • useReducer:管理Redux的状态变化

自定义Hooks允许你为应用程序添加特殊功能。它们可以由一个或多个Hooks组成、可以被重复使用、分解为更小的Hooks。自定义Hooks需要以use开头。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React, { useState } from "react";

function CntApp() {
// declare a new state variable "count"
// useState is the hook which needs to call inside a function component to add some local state to it
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} many times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

export default CntApp;

3.2. 表单

大多数React表单都是单页面应用程序(SPA)或者加载单个页面的网络应用程序。表单使用组件处理数据、使用事件处理程序控制变量的变化和状态的更新。

表单标签有:

  • <input>
  • <textarea>
  • <select>

在HTML,状态由表单元素管理;在React,组件的状态管理着表单元素。

3.2.1. 输入类型

非受控输入 受控输入
允许浏览器处理大部分表单元素,并通过React的变化事件收集数据 使用React直接设置和更新输入值,从而完全控制元素
在输入的DOM节点中管理自己的状态 函数管理数据的传递
元素会在输入值发生变化的时候更新 更好地控制表单元素和数据
ref函数用于从DOM中获取表单值 属性获取当前值并通知更改
父组件控制更改

表单示例:

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

export default function App() {
// to track the state of the email address, this example uses a hook
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");

const handleSubmit = (event) => {
console.log("Email: ${email}\nPassword: ${password}");
event.preventDefault();
}

return (
<form onSubmit={handleSubmit}>
<h1>Registration</h1>
<label>
<!-- to ensure that the email remains updated when the use interacts with form, you must add as input, value, and onChange attributes to the email address -->
Email: <input name="email" type="email" value={email} onChange={e => setEmail(e.target.value)} required />
</label>
<label>
Password: <input name="password" type="password" value={password} onChange={e => setPassword(e.target.value)} required />
</label>

<button>Submit</button>
</form>
);
}

React Hook Form是一个创建表单的实用软件包,它可以帮助你创建可重用的表单组件。

3.3. Redux

Redux是一个状态管理库,它遵循一种称为Flux架构的模式,通常在组件数量较多的时候实用。

Redux提供了一个集中的状态管理系统,它将应用程序的所有状态存储在一个单一的对象中,称为存储(store)。存储是一个JavaScript对象,它包含了应用程序的所有状态。

Redux的工作流程:当用户与应用程序的某个组件交互时,Action会更新整个应用程序的状态,这反过来又会触发组件的重新渲染,从而更新该组件的属性,这些属性会将结果反馈给用户。

3.3.1. 概念

  1. Action:【你的应用程序能做什么】。它是一个由选择单选按钮、复选框或点击按钮触发的事件/JSON对象;它包含着需要对状态进行更改的信息,并由被称为操作创建器(action creator)的函数创建。Action由应用程序的各个部分派发,并由存储空间接收
  2. Store:应用程序状态的唯一位置和权威来源。它是一个包含着状态、函数和其他对象的对象,可以调度和接收操作。Store的更新能够被订阅
  3. Reducers:返回全新的状态的函数。它们从Store接收Action,并对状态进行适当更改。作为事件监听器,Reducer会读取Action的有效载荷(payload)并更新Store
    • Reducer接收两个参数:先前的应用程序状态和Action

3.3.2 中间件

中间件(middleware)是一个函数,它可以访问ActionStore,并且可以在Action到达Reducer之前执行某些操作。它可以用于日志记录、分析、异步请求等。

  1. Thunk中间件:允许在操作创建器中传递函数以创建async Redux、允许编写操作创建器、允许延迟调度操作、允许调度多个操作。优势是Thunk中间件可以无需大量模板代码即可实现异步操作、学习难度小、易于使用;缺点是不能直接对操作做出响应、难以处理可能出现的并发问题、是命令式的、不太容易测试和扩展
  2. Saga中间件:使用称为生成器(generator)的ES6功能来实现异步操作、允许以纯函数的形式表达复杂的逻辑、易于测试、允许分离关注点、易于扩展具有副作用的复杂操作、易于通过try/catch处理错误;缺点是不适合简单的应用程序、需要更多的模板代码、需要具备生成器的知识
  3. 基于Promise的中间件

3.3.3. 数据流

React-Redux应用程序的数据流是单向的。它只朝一个方向流动。

  1. 操作创建器(action creator)朝根归纳器(root reducer)流动
  2. 根归纳器处理Action并返回新的状态到储存空间(store)
  3. 存储空间更新用户界面(UI)
  4. 用户界面调用操作创建器

为什么要选择单向数据流:双向数据绑定会影响浏览器性能,而且很难跟踪数据流,因此Redux的单向数据流解决了这个问题。