DWBBlog


  • Home

  • Archives

25.Angular的使用

Posted on 2019-12-13

1. Angular

Angular、React、Vue并称前端三大框架。

Vue 数据变动 —>页面变动 : 数据劫持+发布订阅模式

React 数据变动 –>页面变动 : setDate() 触发forceUpate

Angular 数据变动 –>页面变动 : 脏检查

2. Angular脚手架的使用

1
2
3
4
5
1.确保本地环境   node\python
2. npm install -g @angular/cli
3. ng new 项目名字
最好项目名字+项目路径不要有中文
4. ng serve --port 4201 //进入项目,启动项目

BP

3. 组件的创建

1
2
3
4
ng generate component foo
ng generate component /home/login

在app.component.html中使用上面创建的组件

4. 插值表达式

1
2
3
4
5
6
7
8
9
10
11
12
<p>login works!</p>
<p>login {{message}}</p>
<p [innerHTML]="message"></p>
<p [innerHTML]="'message'"></p>

<img src="{{ heroImageUrl }}" alt="">
<img [src]="heroImageUrl" alt="">
<img bind-src="heroImageUrl">

<button [disabled]="isButtonDisabled">Button</button>

<p>{{name.split('').reverse().join("-")}}</p>

5. 常用指令

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
<!--1.通过ngFor来遍历数组,可以通过let i=index来获取数组中的序号-->
<p>Heroes:</p>
<ul>
<li [id]="'list-' + hero" *ngFor="let hero of heroes;let i=index">
{{i}}----> {{hero}}
</li>
</ul>

<!--2.通过ngIf来做条件判断-->
<div *ngIf="heroes.length>3; else templateName">有很多英雄</div>
<ng-template #templateName>没有很多英雄</ng-template>

<!--3.通过isSpecial变量来控制div是否有hidden样式:添加class来实现的-->
<div [class.hidden]="!isSpecial">Show with class</div>
<div [class.hidden]="isSpecial">Hide with class</div>

<!--4.通过isSpecial变量来控制div的style的display样式:添加行内样式来实现的-->
<div [style.display]="isSpecial?'block':'none'">Show with class</div>
<div [style.display]="isSpecial?'none':'block'">Hide with class</div>

<!--5.通过ngSwitch来控制多个条件下显示的内容-->
<div [ngSwitch]="currentHero">
<div *ngSwitchCase="'Windstorm'">Windstorm</div>
<div *ngSwitchCase="'Bombasto'">Bombasto</div>
<div *ngSwitchCase="'Magneta'">Magneta</div>
<div *ngSwitchDefault>Tornado</div>
</div>

6. 自定义指令

当angular给我们提供的指令不够用的时候,不能满足我们需求的时候就需要自定义指令了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ng generate directive highlight

import { Directive, ElementRef } from '@angular/core';

@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
//el: 绑定指令的那个页面元素
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = 'yellow';
}

}

使用自定义指令:
<p appHighlight>Highlight me!</p>

7 事件处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!---
1.绑定属性 <img src="img"></img> <p bind-src="img"></p>
2.绑定事件 <p (click)="save()"></p> <p on-click="save()"></p>
3.js表达式 <p>{{ msg }}</p>
4.*ngFor
*ngIf
<p [ngSwitch]="">
<li *ngSwitchCase="xx"></li>
</p>
-->
foo页面中的{{message}}
<button (click)="onSave()">Save</button>
<button (click)="onSave2($event)">On Save</button>
<button (click)="onSave3($event,'heloworld')">On Save</button>
<button (click)="message = '哈哈哈'">内联事件处理</button>
<button on-click="onSave()">On Save</button>

8. 双向绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
1.	需要在app.module.ts文件中引入FormsModule
//引入双向绑定的模块
import { FormsModule } from '@angular/forms';

//声明App依赖的模块
imports: [
BrowserModule,
AppRoutingModule,
FormsModule
],

2. 在需要双向绑定的表单元素中使用[(ngModel)]指令
<input type="text" [(ngModel)]="message">

9.class和style的绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--1.样式的绑定-->
<p class="big bold red">p文字</p>
<!--[class]需要绑定一个变量或者字符串-->
<p [class]="'big'">p文字2</p>
<p [class]="['big','bold']">p文字3</p>
<div [class.big]="true">The class binding is special</div>

<p [ngClass]="{'red':true,'bold':true}">p文字4</p>
<!--2.style绑定-->
<button [style.background-color]="true ? 'cyan': 'grey'" >Save</button>
<button [style.backgroundColor]="true ? 'cyan': 'grey'" >Save</button>
<button [style.font-size.em]="true ? 3 : 1" >Big</button>
<button [style.font-size.%]="true ? 150 : 50" >Small</button>
<div [ngStyle]="{'color':'red',backgroundColor:'yellow'}">这是div</div>

10. 过滤器的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//Angular内置的一些过滤器

<!--过滤器-->
<!--1.处理时间的过滤器-->
<h1>{{currentTime | date:'yyyy-MM-dd HH:mm:ss'}}</h1>

<!--2.处理货币符号的过滤器-->
<h1>{{123 | currency }}</h1>
<h1>{{123 | currency:'¥' }}</h1>

<!--3.把对象转换为json字符串-->
<h1>{{{name:'jack'} | json}}</h1>

<!--4.将小数四舍五入,默认保留3位小数-->
<h1>{{'123.134545'| number}}</h1>

<!--5.转换为大小写的过滤器-->
<h1>{{ 'Hello' | uppercase }} </h1>
<h1>{{'Hello' | lowercase}} </h1>

11. 自定义过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1.	使用如下命令生成一个过滤器
ng generate pipe msgformat

2. 重写msgformat.pipe.ts文件
export class MsgformatPipe implements PipeTransform {
//value就是要过滤的内容
transform(value: string, ...args: unknown[]): unknown {
return value.split("").reverse().join("");
}
}


3. 在指定页面中使用自定义的过滤器
<!--使用自定义过滤器-->
<h1>{{'zhangsan' | msgformat}}</h1>
1
2


1
2


1
2


12. 路由的使用

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
1.	在app.module.ts文件中引入路由模块
//引入路由模块
import { RouterModule, Routes } from '@angular/router';

2. 在app.module.ts文件中写路由配置
//声明路由
const appRoutes: Routes = [
{ path: 'foo', component: FooComponent },
//路由路径、组件和路由数据
{ path: 'login/:id', component: LoginComponent ,data: { title: 'login信息' }},
];

3. 把路由配置信息放到imports中
//声明App依赖的模块
imports: [
RouterModule.forRoot(
appRoutes
),
BrowserModule,
AppRoutingModule,
FormsModule
],

4. 在app.component.html中切换路由

<a routerLink="/foo" routerLinkActive="active">来到foo</a>
<a routerLink="/login/5" [queryParams]="{name: 'zhangsan'}" routerLinkActive="active">来到login</a>

<!-- router-outlet类似于vue中的router-view :作用是用来呈现匹配到的路由的组件信息 -->
<router-outlet></router-outlet>

13. http服务的使用步骤

服务就是针对某个单一或系统功能的封装,Angular中典型的服务有http服务和日志服务

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
1.	在app.module.ts文件中引入HttpClientModule,并且加入到imports里面
//HttpClientModule
import {HttpClientModule} from '@angular/common/http';

//声明App依赖的模块
imports: [
RouterModule.forRoot(
appRoutes
),
BrowserModule,
AppRoutingModule,
FormsModule,
HttpClientModule
],

2. 在需要使用http服务的组件中引入HttpClient,并且通过依赖注入把HttpClient对象注入到组件实例中
import { HttpClient } from '@angular/common/http';

//在构造函数中依赖注入当前激活的路由的信息
constructor(
private http: HttpClient
) { }

3. 在组件的ngOnInit方法里面发送请求
ngOnInit(): void {
// 发送get请求
this.http.get('/api/homead').subscribe(data => {
console.log(data)
},
//请求错误
err => {
console.log('Something went wrong!');
});
}

4. 当协议、域名、端口有任何一个不一致的时候会构成跨域请求,此时需要设置代理
4.1 在项目根目录下创建proxy.conf.json文件
{
"/api": {
"target": "http://localhost:3001/",
"changeOrigin": true,
"logLevel": "debug"
}
}
4.2 修改package.json中scripts中的 start配置
"scripts": {
"ng": "ng",
"start": "ng serve --proxy-config proxy.conf.json",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
4.3 重启项目

14. TS变量的声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let nickname: string = '张三';
let isDone: boolean = false;
let age: number = 37;
//模板字符串
let sentence: string = `Hello, my nickname is ${ nickname }.
I'll be ${ age + 1 } years old next month.`



{
//声明一个list变量,他是number类型的数组
let list: number[] = [1, 2, 3];
}
{
//第二种方式是使用数组泛型,Array<元素类型>:
let list: Array<number> = [1, 2, 3];
let list2: Array<string> = ['1', '2', '3'];
}

15. TS元组类型

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同

1
2
let x: [ number,string];
x = [1, 'abc'];

16. Any类型

1
2
3
4
//any类型
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false;

17. Void类型

1
2
3
4
5
6
7
8
//void类型 : 没有返回值,此时会返回undefined
//function 函数名(函数参数): 函数返回值{ }
function warnUser(): void {
alert("This is my warning message");
}
//当我们声明一个变量是void类型的时候,他的值只能是undefined
let unusable: void;
unusable = undefined;

18. 展开运算符和解构赋值

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

let input = [1, 2];
let [first, second] = input;
console.log(first); // outputs 1
console.log(second); // outputs 2

//通过解构赋值交换两个变量
let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a, b)

//函数参数解构:
function ff([first, second]: [number, number]) {
console.log(first)
console.log(second)
}
ff([1, 2])

//结构剩余参数
{
let [first, ...rest] = [1, 2, 3, 4]
console.log(first) // 1
console.log(rest) // [2, 3, 4]
}

//对象的解构赋值
{
let o = {
a: "foo",
b: 12,
c: "bar"
};
//let { a, b, c } = o;
let { a, ...passthrough } = o;
console.log(a, passthrough)
//解构赋值的时候给变量取别名
let { a:a1 } = o;
}

//展开运算符
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let arr3 = [...arr1, ...arr2];

let obj1 = { a: 1, b: 2 };
let obj2 = { c: 3 }
let obj3 = { ...obj1, ...obj2 };
console.log(arr3,obj3)

19. 函数类型

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
//1.函数声明:function 函数名字(参数1,参数2,参数3):函数返回值{函数体}
{
function add(x: number, y: number): number {
return x + y
}
console.log(add(1, 23))
}
//2.函数可选参数
{
function add(x: number, y?: number): number {
return x + 10
}
console.log(add(1))
}

//3.函数默认参数
{

function add(x: number, y: number = 20): number {
return x + y
}
console.log(add(10))
}

//4.剩余参数:一般情况下是所有参数的最后一个
{
function sum(a: number, ...args: number[]): number {
let ret: number = 0
args.forEach((item: number): void => {
ret += item
})
return ret
}

console.log(sum(1, 2, 3))
}

//5.箭头函数
{
//let 变量 = (参数1,参数2):返回值 => { return xx ;}
let add = (x: number, y: number): number => x + y
}

20.类和继承和成员变量修饰符

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
//基本示例
class Person {
//成员变量如果不写修饰符,默认就是public,此时可以在任何地方访问
public name: string;
age: number;
//加private修饰的变量是私有变量,只能在当前类内部使用
private money: number = 1000000;
//加protected修饰的变量可以在本类以及本类的子类中访问
protected house: string = "希尔顿酒店";


//构造函数
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}

sayHello() {
console.log(this.name);
}
}

let zs: Person = new Person('张三', 18);
console.log(zs);

//定义一个学生类继承Person类
class Student extends Person {
num: number;
constructor(num: number,name:string,age:number) {
//当有继承关系的时候,一定要先调用super初始化父类的信息
super(name,age);
//再初始化子类的信息
this.num = num;
}
printMsg() {
console.log(this.house)
}
}
var s: Student = new Student(100, "张三", 18);
s.sayHello()
s.printMsg();
console.log(s.name)

//子类继承父类之后就自动拥有父类非私有的成员信息

21. set和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 Person {
//声明一个成员变量,他的名字是_name
private _name: string;

constructor(name: string) {
this._name = name;
}

//通过set和get方法间接来访问_name
set name(value: string) {
this._name = value;
}
get name() {
return this._name;
}
}

let p: Person = new Person("张三");
//注意:这里面不是直接去访问name成员变量,而是去访问get name(){}方法来间接访问成员变量_name
console.log(p.name);
//注意:这里面不是直接给name成员变量赋值,而是去访问set name(value:string){...}方法间接给_name成员变量赋值
p.name = "哈哈";
console.log(p.name)
//搞清楚:_name才是真正的成员变量,p.name和p.name="xx"是调用对应的set和get方法

22. 静态变量和静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

class Person {
//成员变量 拥有者是对象,所以我们需要new Person()之后才能访问name信息
name = "zhangsan";
//加static修饰叫静态变量 拥有者是类,不是对象
static country = "中国";
//加static修饰的方法叫静态方法 拥有者是类,不是对象
static sayhello() {
console.log("hello")
}
constructor () { }
}

let p1 = new Person();
let p2 = new Person();

console.log(Person.country) //静态变量,直接通过类型来访问
// console.log(p1.country) //错误
console.log(p1.name)
console.log(Person.name)

24.next.js

Posted on 2019-12-10

1.next.js

  1. 安装

    1
    npm install --save react react-dom next
  2. 在项目根目录下添加文件夹pages(一定要命名为pages,这是next的强制约定,不然会导致找不到页面),然后在package.json文件里面添加script用于启动项目:

    1
    2
    3
    4
    5
    6
    7
    {
    "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
    }
    }
  3. 在pages文件夹下创建index.js文件,文件内容:

    1
    2
    3
    4
    5
    6
    7
    const Index = () => (
    <div>
    <p>Hello next.js</p>
    </div>
    )

    export default Index
  4. 前端路由

    1
    2
    3
    4
    5
    6
    7
    const About = () => (
    <div>
    <p>This is About page</p>
    </div>
    )

    export default About;
  5. 路由的跳转

    next.js使用next/link实现页面之间的跳转,用法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import Link from 'next/link'

    const Index = () => (
    <div>
    <Link href="/about">
    <a>About Page</a>
    </Link>
    <p>Hello next.js</p>
    </div>
    )

    export default Index
  6. 解释路由跳转的原理:

    官方文档说用前端路由跳转是不会有网络请求的,实际会有一个对about.js文件的请求,而这个请求来自于页面内动态插入的script标签。但是about.js只会请求一次,之后再访问是不会请求的,毕竟相同的script标签是不会重复插入的。

  7. Link标签不支持添加style和className等属性,如果要给链接增加样式,需要在子元素上添加:

    1
    2
    3
    <Link href="/about">
    <a className="about-link" style={{color:'#ff0000'}}>Go about page</a>
    </Link>

1.Layout

所谓的layout就是就是给不同的页面添加相同的header,footer,navbar等通用的部分,同时又不需要写重复的代码。在next.js中可以通过共享某些组件实现layout。

我们先增加一个公共的header组件,放在根目录的components文件夹下面(页面级的组件放pages中,公共组件放components中):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import Link from 'next/link';

const linkStyle = {
marginRight: 15
}

const Header = () => (
<div>
<Link href="/">
<a style={linkStyle}>Home</a>
</Link>
<Link href="/about">
<a style={linkStyle}>About</a>
</Link>
</div>
)

export default Header;

然后在index和about页面中引入header组件,这样就实现了公共的layout的header:

1
2
3
4
5
6
7
8
9
10
import Header from '../components/Header';

const Index = () => (
<div>
<Header />
<p>Hello next.js</p>
</div>
)

export default Index;

如果要增加footer也可以按照header的方法实现。
除了引入多个header、footer组件,我们可以实现一个整体的Layout组件,避免引入多个组件的麻烦,同样在components中添加一个Layout.js文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Header from './Header';

const layoutStyle = {
margin: 20,
padding: 20,
border: '1px solid #DDD'
}

const Layout = (props) => (
<div style={layoutStyle}>
<Header />
{props.children}
</div>
)

export default Layout

这样我们只需要在页面中引入Layout组件就可以达到布局的目的:

1
2
3
4
5
6
7
8
9
import Layout from '../components/Layout';

const Index = () => (
<Layout>
<p>Hello next.js</p>
</Layout>
)

export default Index;

2.页面间传值

next中的页面间传值方式和传统网页一样也可以用url参数实现,我们来做一个简单的博客应用:

首先将index.js的内容替换成如下来展示博客列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Link from 'next/link';
import Layout from '../components/Layout';

const PostLink = (props) => (
<li>
<Link href={`/post?title=${props.title}`}>
<a>{props.title}</a>
</Link>
</li>
);

export default () => (
<Layout>
<h1>My Blog</h1>
<ul>
<PostLink title="Hello next.js" />
<PostLink title="next.js is awesome" />
<PostLink title="Deploy apps with Zeit" />
</ul>
</Layout>
);

通过在Link的href中添加title参数就可以实现传值。

现在我们再添加博客的详情页post.js:

1
2
3
4
5
6
7
8
9
10
11
import { withRouter } from 'next/router';
import Layout from '../components/Layout';

const Post = withRouter((props) => (
<Layout>
<h1>{props.router.query.title}</h1>
<p>This is the blog post content.</p>
</Layout>
));

export default Post;

上面代码通过 withRouter 将 next 的 router 作为一个 prop 注入到 component 中,实现对 url 参数的访问。

通过 props.router.query.title 进行取到传递的值

使用query string可以实现页面间的传值,但是会导致页面的url不太简洁美观,尤其当要传输的值多了之后。所以next.js提供了Route Masking这个特性用于路由的美化。

3.路由伪装(Route Masking)

这项特性的官方名字叫Route Masking,没有找到官方的中文名,所以就根据字面意思暂且翻译成路由伪装。所谓的路由伪装即让浏览器地址栏显示的url和页面实际访问的url不一样。实现路由伪装的方法也很简单,通过 Link 组件的 as 属性告诉浏览器 href 对应显示为什么 url 就可以了,index.js 代码修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Link from 'next/link';
import Layout from '../components/Layout';

const PostLink = (props) => (
<li>
<Link as={`/p/${props.id}`} href={`/post?title=${props.title}`}>
<a>{props.title}</a>
</Link>
</li>
);

export default () => (
<Layout>
<h1>My Blog</h1>
<ul>
<PostLink id="hello-nextjs" title="Hello next.js" />
<PostLink id="learn-nextjs" title="next.js is awesome" />
<PostLink id="deploy-nextjs" title="Deploy apps with Zeit" />
</ul>
</Layout>
);

这是因为刷新页面会直接向服务器请求这个url,而服务端并没有该url对应的页面,所以报错。为了解决这个问题,需要用到next.js提供的自定义服务接口(custom server API)。

2.自定义服务接口

自定义服务接口前我们需要创建服务器,安装Express:

1
npm install --save express

在项目根目录下创建server.js 文件,内容如下:

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 next = require('next');

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

app.prepare()
.then(() => {
const server = express();
server.get('*', (req, res) => {
return handle(req, res);
});
server.listen(3000, (err) => {
if (err) throw err;
console.log('> Ready on http://localhost:3000');
});
})
.catch((ex) => {
console.error(ex.stack);
process.exit(1);
});

然后将package.json里面的dev script改为:

1
2
3
"scripts": {
"dev": "node server.js"
}

运行npm run dev后项目和之前一样可以运行,接下来我们需要添加路由将被伪装过的url和真实的url匹配起来,在server.js中添加:

1
2
3
4
5
6
7
8
......
const server = express();
server.get('/p/:id', (req, res) => {
const actualPage = '/post';
const queryParams = { title: req.params.id };
app.render(req, res, actualPage, queryParams);
});
......

这样我们就把被伪装过的url和真实的url映射起来,并且query参数也进行了映射。重启项目之后就可以刷新详情页而不会报错了。但是有一个小问题,前端路由打开的页面和后端路由打开的页面title不一样,这是因为后端路由传过去的是id,而前端路由页面显示的是title。这个问题在实际项目中可以避免,因为在实际项目中我们一般会通过id获取到title,然后再展示。作为Demo我们偷个小懒,直接将id作为后端路由页面的title。

之前我们的展示数据都是静态的,接下来我们实现从远程服务获取数据并展示。★★

3.远程数据获取

next.js提供了一个标准的获取远程数据的接口:getInitialProps,通过getInitialProps我们可以获取到远程数据并赋值给页面的props。getInitialProps即可以用在服务端也可以用在前端。接下来我们写个小Demo展示它的用法。我们打算从TVMaze API 获取到一些电视节目的信息并展示到我的网站上。首先,我们安装isomorphic-unfetch,它是基于fetch实现的一个网络请求库:

1
npm install --save isomorphic-unfetch

然后我们修改index.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
import Link from 'next/link';
import Layout from '../components/Layout';
import fetch from 'isomorphic-unfetch';

const Index = (props) => (
<Layout>
<h1>Marvel TV Shows</h1>
<ul>
{props.shows.map(({ show }) => {
return (
<li key={show.id}>
<Link as={`/p/${show.id}`} href={`/post?id=${show.id}`}>
<a>{show.name}</a>
</Link>
</li>
);
})}
</ul>
</Layout>
);

Index.getInitialProps = async function () {
const res = await fetch('https://api.tvmaze.com/search/shows?q=marvel');
const data = await res.json();
return {
shows: data
}
}

export default Index;

以上代码的逻辑应该很清晰了,我们在getInitialProps中获取到电视节目的数据并返回,这样在Index的props就可以获取到节目数据,再遍历渲染成节目列表。

4.增加样式

到目前为止,咱们做的网页都太平淡了,所以接下来咱们给网站增加一些样式,让它变得漂亮。

对于React应用,有多种方式可以增加样式。主要分为两种:

  1. 使用传统CSS文件(包括SASS,PostCSS等)
  2. 在JS文件中插入CSS

使用传统CSS文件在实际使用中会用到挺多的问题,所以next.js推荐使用第二种方式。next.js内部默认使用styled-jsx框架向js文件中插入CSS。这种方式引入的样式在不同组件之间不会相互影响,甚至父子组件之间都不会相互影响。

1.styled-jsx

接下来,我们看一下如何使用styled-jsx。将index.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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import Link from 'next/link';
import Layout from '../components/Layout';
import fetch from 'isomorphic-unfetch';

const Index = (props) => (
<Layout>
<h1>Marvel TV Shows</h1>
<ul>
{props.shows.map(({ show }) => {
return (
<li key={show.id}>
<Link as={`/p/${show.id}`} href={`/post?id=${show.id}`}>
<a className="show-link">{show.name}</a>
</Link>
</li>
);
})}
</ul>
<style jsx>
{`
*{
margin:0;
padding:0;
}
h1,a{
font-family:'Arial';
}
h1{
margin-top:20px;
background-color:#EF141F;
color:#fff;
font-size:50px;
line-height:66px;
text-transform: uppercase;
text-align:center;
}
ul{
margin-top:20px;
padding:20px;
background-color:#000;
}
li{
list-style:none;
margin:5px 0;
}
a{
text-decoration:none;
color:#B4B5B4;
font-size:24px;
}
a:hover{
opacity:0.6;
}
`}
</style>
</Layout>
);

Index.getInitialProps = async function () {
const res = await fetch('https://api.tvmaze.com/search/shows?q=marvel');
const data = await res.json();
console.log(`Show data fetched. Count: ${data.length}`);
return {
shows: data
}
}

export default Index;

增加了一点样式之后比之前好看了一点点。我们发现导航栏的样式并没有变。因为Header是一个独立的的component,component之间的样式不会相互影响。如果需要为导航增加样式,需要修改Header.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
import Link from 'next/link';

const Header = () => (
<div>
<Link href="/">
<a>Home</a>
</Link>
<Link href="/about">
<a>About</a>
</Link>
<style jsx>
{`
a{
color:#EF141F;
font-size:26px;
line-height:40px;
text-decoration:none;
padding:0 10px;
text-transform:uppercase;
}
a:hover{
opacity:0.8;
}
`}
</style>
</div>
)

export default Header;

2.全局样式

当我们需要添加一些全局的样式,比如 rest.css 或者鼠标悬浮在 a 标签上时出现下划线,这时候我们只需要在style-jsx标签上增加 global关键词就行了,我们修改Layout.js如下:

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

const layoutStyle = {
margin: 20,
padding: 20,
border: '1px solid #DDD'
}

const Layout = (props) => (
<div style={layoutStyle}>
<Header />
{props.children}
<style jsx global>
{`
a:hover{
text-decoration:underline;
}
`}
</style>
</div>
)

export default Layout

这样鼠标悬浮在所有的a标签上时会出现下划线

5.部署项目

部署next.js应用

Build

部署之前我们首先需要能为生产环境build项目,在package.json中添加script:

1
"build": "next build"

接下来我们需要能启动项目来serve我们build的内容,在package.json中添加script:

1
"start": "next start"

然后依次执行:

1
2
npm run build
npm run start

build完成的内容会生成到.next文件夹内,npm run start之后,我们访问的实际上就是.next文件夹的内容。

运行多个实例
如果我们需要进行横向扩展( Horizontal Scale)以提高网站的访问速度,我们需要运行多个网站的实例。首先,我们修改package.json的start script:

1
"start": "next start -p $PORT"

如果是windows系统:

1
"start": "next start -p %PORT%"

然后运行build: npm run build,然后打开两个命令行并定位到项目根目录,分别运行:

然后运行build: npm run build,然后打开两个命令行并定位到项目根目录,分别运行:

1
2
PORT=8000 npm start
PORT=9000 npm start

运行完成后打开localhost:8000和localhost:9000都可以正常访问:

部署并使用自定义服务

我们将start script修改为:

1
"start": "NODE_ENV=production node server.js"

这样我们就解决了自定义服务的部署。重启项目后刷新详情页也能够正常访问了。

到此为止,我们就了解了next.js的基本使用方法,如果有疑问可以查看next.js官方文档,也可以给我留言讨论。

本文Demo源码:Github 源码
next.js官网:https://nextjs.org/
next.js官方教程:https://nextjs.org/learn
next.js Github:https://github.com/zeit/next.js
————————————————
版权声明:本文为CSDN博主「MudOnTire」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/mudontire/article/details/80980910

23.react-native

Posted on 2019-12-07

1.App开发模式介绍

Web App : 用html+css+js写的网页程序

混合式App开发:(HybirdApp)就是使用前端已有的技术,HTML + CSS + JS ,然后再搭配一些相关的打包编译技术,就能够开发出一个手机App,安装到手机中进行使用。

原生(native app)App:使用IOS、Android 官方提供的工具、开发平台、配套语言进行手机App开发的方式

BP

BP

2.hybird开发技术选型

Vue.js 和 Weex

React.js 和 React-Native

Angular.js 和 Ionic

Html5+ :把html+css+js开发web应用打包成apk

BP

3.安卓环境配置

1.安装java jdk

​ A) 修改环境变量,新增JAVA_HOME的系统环境变量,值为C:\Program Files (x86)\Java\jdk1.8.0_112,也就是安装JDK的根目录

B) 修改系统环境变量Path,在Path之后新增%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;

C) 新建系统环境变量CLASSPATH,值为.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;

D) 保存所有的系统环境变量,同时退出系统环境变量配置窗口,然后运行cmd命令行工具,输入javac,如果能出现javac的命令选项,就表示配置成功!

注意:在win10 的环境下可以将相对路径换成绝对路径

classpath:

BP

path:

BP

2.安装Node.js
3.安装Git环境
4.安装python2环境
5.将android压缩包解压之后放到c盘根目录

A) 在系统环境变量中新建ANDROID_HOME,值为android SDK Manager的安装路径C:\android

B) 紧接着,在Path中新增;%ANDROID_HOME%\tools;%ANDROID_HOME%\platform-tools;

注意:win10 如上图path的最后的写法

6.(非必须)模拟器

A) 将C:\android\platform-tools\adb.exe拷贝一份,重命名为nox_adb.exe,替换掉D:\Program Files\Nox\bin下的nox_adb.exe文件

B) 打开夜神模拟器

C) 连接夜神模拟器 adb connect 127.0.0.1:62001 / adb disconnect 127.0.0.1:62001

D) 查看安卓连接设备 adb devices

E) 配置夜神模拟器 375*667 dpi:163

7.安装yarn和react-native-cli

npm install -g yarn react-native-cli

设置下载源:

yarn config set registry https://registry.npm.taobao.org –global

yarn config set disturl https://npm.taobao.org/dist –global

8.react-native-init

通过react-native-init命令创建一个App (注意项目不要放在中文路径下)

9.react-native run-android

进入testRN目录,通过npx
react-native run-android命令来运行项目

10.问题

BP

A) 在android/app/src/main下新建assets文件夹

B) 在项目根目录下输入如下命令:

react-native bundle –platform android –dev false –entry-file index.js –bundle-output android/app/src/main/assets/index.android.bundle –assets-dest android/app/src/main/res

BP

C) 重新使用 react-native run-android 来启动项目

11.热更新解决端口被占用的问题

A) netstat -ano | findstr “8081”

B) tasklist|findstr “pid” 如果结果显示 java.exe,则说明8081端口被占用了

C) react-native run-android 把项目安装到夜神模拟器

D)关闭以下窗口,然后通过react-native start –port 9999 重新打开服务端口

BP

E) 打开夜神模拟器,打开app,摇一摇

BP

F) 打开夜神模拟器,摇一摇

BP

4.RN基础组件

View —>视图

Text —>文本

Image —>图片

TextInput —>文本输入框

ScrollView —>滚动视图

StyleSheet —>创建样式

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
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow
*/
// 1.首先在开头导入所需要的组件的名称
import React from 'react';
import {
SafeAreaView,
StyleSheet,
ScrollView,
View,
Text,
StatusBar,
Image,
TextInput
} from 'react-native';

// 2.在App中定义所有的函数和变量的值
const App: () => React$Node = () => {
const [value, onChangeText] = React.useState('Useless Placeholder');

let pic = {
uri: 'https://upload.wikimedia.org/wikipedia/commons/d/de/Bananavarieties.jpg'
};

//3.在return中返回需要映射的组件
return (
<>
<ScrollView>
<Text>这是View</Text>
<View style={styles.box}></View>
<View style={{width:100,height:100,backgroundColor:'blue'}}></View>
<Image source={pic} style={{ width: 193, height: 110 }} />
<Image source={require('./images/1.png')}></Image>
<TextInput
style={{ height: 40, borderColor: 'red', borderWidth: 1 }}
onChangeText={text => onChangeText(text)}
value={value}
/>
</ScrollView>
</>
);
};


const styles = StyleSheet.create({
box:{
width:300,
height:200,
borderColor:'yellow',
borderWidth:2,
borderStyle:"solid"
}
});

export default App;

5.交互组件

Button —>按钮

Picker —>选择视图

Switch —>开关

Slider —>滑块

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
<Button
onPress={onPressLearnMore}
title="Learn More"
color="#841584"
/>

<Picker
selectedValue={language}
style={{ height: 50, width: 100 }}
onValueChange={(itemValue, itemIndex) =>
onChangeLanaguage(itemValue)
}>
<Picker.Item label="Java" value="java" />
<Picker.Item label="JavaScript" value="js" />
</Picker>

<Slider maximumValue={100} minimumValue={0} onValueChange={(value) => console.log(value)} step={2} value={50}></Slider>

<Switch value={switchStatus} onValueChange={(value) => { onChangeswitchStatus(value) }}></Switch>

const [switchStatus, onChangeswitchStatus] = React.useState(false);
const [language, onChangeLanaguage] = React.useState('');


onPressLearnMore = () => {
console.log("xxx")
}

6. 列表组件

FlatList : 列表

Section :分组列表

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
<SectionList
renderItem={({ item, index, section }) => <Text key={index}>{item}</Text>}
sections={[
{ title: 'Title1', data: ['item1', 'item2'], renderItem: overrideRenderItem },
{ title: 'Title2', data: ['item3', 'item4'], renderItem: overrideRenderItem2 },
{ title: 'Title3', data: ['item5', 'item6'] },
]}
/>

<FlatList
data={[{ key: 'a' }, { key: 'b' }, { key: 'a' }, { key: 'b' }]}
renderItem={({ item }) => <Text>{item.key}</Text>}
ItemSeparatorComponent={() => <View style={{ height: 1, width: '100%', backgroundColor: "red" }}></View>}
ListHeaderComponent={() => <View><Text>列表头</Text></View>}
ListFooterComponent={() => <View><Text>列表尾</Text></View>}
numColumns={2}
columnWrapperStyle={{ borderWidth: 1, borderColor: 'blue', paddingLeft: 20 }} //指定每一行容器的样式(设置多列的情况下)
getItemLayout={(data, index) => (
{ length: 100, offset: (100 + 2) * index, index }
)} //当行高固定的时候可以使用这个属性来优化性能
refreshing={false} //当前是否正在刷新
onEndReachedThreshold={0} //到距离底部0的时候触发onEndReached方法
onEndReached={() => { console.log("拉到底部了") }}
/>


const overrideRenderItem = ({ item, index, section: { title, data } }) => <Text style={{ color: 'red' }} key={index}>Override1{item}</Text>
const overrideRenderItem2 = ({ item, index, section: { title, data } }) => <Text style={{ color: 'blue' }} key={index}>Override2{item}</Text>

7.其他组件

ActivityIndicator —> 提示的Activity

Alert —>提示弹框

Modal —>模态页面

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
<Button onPress={showModal} title="弹出Modal"></Button>
<Button onPress={showAlert} title="弹出Alert"></Button>

<Modal
animationType='slide' // 从底部滑入
transparent={false} // 不透明
visible={showmodal} // 根据isModal决定是否显示
onRequestClose={() => { console.log("onRequestClose") }} // android必须实现
>
<View style={styles.modalViewStyle}>
{/* 关闭页面 */}
<TouchableOpacity
onPress={() => {
onChangeShowmodal(false)
}}
>
<Text>关闭页面</Text>
</TouchableOpacity>
</View>
</Modal>

<ActivityIndicator animating={true} color="blue"></ActivityIndicator>


showAlert = () => {
Alert.alert(
'Alert Title',
'My Alert Msg',
[
{ text: 'Ask me later', onPress: () => console.log('Ask me later pressed') },
{ text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel' },
{ text: 'OK', onPress: () => console.log('OK Pressed') },
],
{ cancelable: false }
)
}

showModal = () => {
onChangeShowmodal(true)
}

const [showModal1,onChangeShowmodal] = React.useState(false)

Animated的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const [fadeInOpacity, onChangeFadeInOpacity] = React.useState(new Animated.Value(0))
React.useEffect(() => {
Animated.timing(fadeInOpacity, {
toValue: 1, // 目标值
duration: 2500, // 动画时间
}).start();
}, [])

<Animated.View // 使用专门的可动画化的View组件
style={{
opacity: fadeInOpacity // 将透明度指定为动画变量值
}}
>
<Text>测试透明度动画</Text>
</Animated.View>

22.react项目练手

Posted on 2019-12-04

1.小问题

1
2
3
[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. See <URL>

由于目标被当作被动事件监听器,无法防止被动事件监听器中的默认值。看到< URL >
1
2
3
body{
touch-action: none;
}

2.路由使用

1
2
3
4
5
6
7
8
9
10
11
12
import { withRouter } from 'react-router-dom'

this.props.history.push("/")

export default withRouter (RTab);

<HashRouter>
<RNav></RNav>
<RouterMap></RouterMap>
<Button type='primary'>按钮</Button>
<RTab></RTab>
</HashRouter>

3.路由传值

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
//  1. 导入get的方法
import {fetchget,fetchpost} from '../../untils/myfetch.js'

// 2. 获取数据
async componentDidMount() {
// 获取广告数据
const result =await fetchget('/api/homead')
this.setState({
ads: result
})
}

// 3. 传值
<Ad ads={this.state.ads}></Ad>

// 4. 渲染到页面上
<div className="ad-container clear-fix">
{this.props.ads.map((item, index) => {
return <div key={index} className="ad-item float-left">
<a href={item.link} target="_blank">
<img src={require(item.img+'')} alt={item.title}/>
</a>
</div>
})}
</div>

拓展

  1. 根据 ads 的长度进行判断是否加载
  2. 导入 ActivityIndicator 组件
1
2
3
4
5
6
7
8
import {ActivityIndicator} from 'antd-mobile'

{
//传递数据给子组件
this.state.ads.length
? <Ad ads={this.state.ads}/>
: <ActivityIndicator text="loading..."/>
}

21.Flux和Redux

Posted on 2019-12-02

1.react-virtualized

是一个react中的虚拟化的库,使用它来做列表、Grid可以提高性能,减少dom操作。

  • Grid:用于优化构建任意网状的结构,传入一个二维的数组,渲染出类似棋盘的结构。

  • List:List是基于Grid来实现的,但是是一个维的列表,而不是网状。

  • Collection:类似于瀑布流的形式,同样可以水平和垂直方向滚动。

2.Flux

Flux 是一种架构思想,专门解决软件的结构问题。它跟MVC 架构是同一类东西,但是更加简单和清晰

Flux把应用分为4个部分:

  1. View : 视图

  2. Action :动作

  3. Dispatcher :派发器,用来接收Action

  4. Store : 数据层

Flux最大特点:数据单向流动

3.Redux

我们把Flux看做一个框架的理念的话,Redux是Flux的一种实现

1
2
3
4
Redux在此基础上强调三个基本原则:
1.唯一数据源 :唯一数据源指的是应用的状态数据应该只存储在唯一的一个Store上。
2.保持状态只读 : 保持状态只读,就是说不能去直接修改状态,要修改Store的状态,必须要通过派发一个action对象完成。
3.数据改变只能通过纯函数完成 :这里所说的纯函数就是把Reducer,Reducer描述了state状态如何修改。Redux这个名字的前三个字母Red代表的就是Reducer,其实Redux名字的含义就是Reducer+Flux。

BP

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
//1.安装redux   yarn add redux


//2.引入createStore,用来创建一个Store对象
import { createStore } from 'redux'

//3.创建actions。action是改变state的唯一途径,返回一个普通的javascript对象,
//它描述了一个行为且是改变state的唯一途径。
//这个行为我们需要通过dispatch函数来触发action,从而改变对应的state。
//action必须带有type指明具体的行为名称,且能附带上额外的信息。
function addFn() {
return { type:'ADD' }
}
function minusFn() {
return { type:'MINUS' }
}


/*
4.创建reducer
这是一个 reducer,形式为 (state, action) => state 的纯函数。
该reducer可以接收action,描述如何通过action把state转变成下一个state。
state的形式取决于你,可以是基本类型、数组、对象、甚至是Immutable.js 生成的数据结构。
惟一的要点是当state变化时需要返回全新的对象,而不是修改传入的参数。
*/
function counter(state = 0, action) {
switch (action.type) {
case 'ADD':
return state + 1
case 'MINUS':
return state - 1
default:
return state
}
}


//5.创建一个store。store对象的API 有 { subscribe, dispatch, getState }。
let store = createStore(counter)

//6.订阅更新,监听state的变化
store.subscribe(() => console.log(store.getState(),"xxxx"))

//7.通过dispatch来触发action修改state
store.dispatch(addFn())
store.dispatch(addFn())
store.dispatch(minusFn())

3.1 异步处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
异步处理:
1. yarn add redux-thunk

2. const store=createStore(couter ,applyMiddleware(thunk))
import { createStore,applyMiddleware } from 'redux'

3. 声明异步action
function addAsynFn() {
return dispatch=>{
setTimeout(()=>{
dispatch(addFn())
},2000)
}
}

react-redux是在react中使用redux的一个插件,使用它可以方便快捷的在react中用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
29
30
31
32
33
34
35
36
37
38
39
//react-redux的使用步骤
//1.安装 yarn add react-redux

//2.react-redux 给我们提供了两个接口:一个Provider,一个是connect
//2.1 Provider组件的作用:在子组件中快速获取到store对象
//2.2 connect组件的作用:连接React组件与Redux store。连接操作不会改变原来的组件类,反而返回一个新的已与Redux store连接的组件类。

//3.在index.js中引入Provider组件,并且作为项目根组件包裹App组件,然后传入store
ReactDOM.render(<Provider store={store}><App/></Provider>, document.getElementById('root'));

//4.在需要视同redux的组件中引入connect组件,然后使用connect将当前组件和redux的store关联
import { connect } from 'react-redux'
import { addFn, minusFn, addAsynFn } from './actions/index'
//会将store中state的数据映射到组件的props中
const mapStatetoProps = (state) => {
return { num: state }
}
//会将actions映射到组件的props中
const mapDispatchToProps = { addFn, minusFn, addAsynFn }
//使用connect返回一个已经把App和store联系起来的一个组件
App = connect(mapStatetoProps, mapDispatchToProps)(App)
export default App;


//5.在组件的页面中就可以使用store了
const num = this.props.num
const addFn = this.props.addFn
const minusFn = this.props.minusFn
const addAsynFn = this.props.addAsynFn

return (
<div className="App">
<h1>你好吗?</h1>
{<p>现在的总数是:{num}</p>}
{<button onClick={addFn}>加1</button>}
{<button onClick={minusFn}>减1</button>}
{<button onClick={addAsynFn}>异步加1</button>}
</div>
);

合并多个reducers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//1.使用combineReducers合并多个reducer
import {combineReducers} from 'redux'

import {counter} from './counter';
import {location} from './location';

export default combineReducers({counter,location})

//2.在index.js中引入合并后的reducer,并且使用合并后的reducer创建store
import reducer from './reducers/index'
let store = createStore(reducer,applyMiddleware(thunk))

//3.在页面使用store的时候需要从state中获取对应的数据
const mapStatetoProps = (state) => {
return {num:state.counter,location:state.location}
}

4.redux的不可变数据

在redux中整个应用共用一个state。如何判断state更新呢?Redux采用不可变数据。

我们在判断数据是否相同时,并不需要深入判断数据对象的值是否相同,只需要浅比较即可,也就是判断是否为同一个数据对象地址,因为不可变数据对象在数据变化时均会重新创建一个新的数据对象,数据对象的地址不会相同。这也就是为什么在Reactjs,Redux中才有不可变数据对象。

5.fetch的使用

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
//安装  yarn add whatwg-fetch es6-promise

fetch('/api/home', {
credentials: "include",
/*
fetch不管在同域还是在跨域的情况下,默认都不携带cookie的,所以那些需要权限验证的请求就无法正常获取到数据,这时候需要配置credentials项,有一下三个选项可添:
omit: 默认值,忽略cookie的发送
same-origin: 表示cookie只能同域发送,不能跨域发送
include: 表示既可以同域发送,也可以跨域发送
*/
})
.then(response => {
//有三种方式解析获取到的数据:
//1 json数据 用reponse.json()来解析
//2 xml格式文件 用response.text()来解析
//3 图片文件 用response.blob()来解析
return response.json();
})
.then((data) => {
console.log(data);
})
}

const handleRequestPOST = () => {
//post请求需要提交的数据
fetch('/api/submit', {
method: 'post', //请求方式
credentials: "include",
headers: {
//post请求格式
'Content-Type': 'application/x-www-form-urlencoded'
},
//post请求发送的数据需要是这样的
body: "name=zhangsan&content=宝宝不开心"
})
.then(response => response.json())
.then(data => {
console.log(data);
}).catch(error => console.log('error is', error));

代理的配置:

  1. yarn add http-proxy-middleware

  2. 在src目录下创建setupProxy.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const proxy = require("http-proxy-middleware");
module.exports = function(app) {
app.use(
proxy("/api/*", {
target: "http://localhost:9999/",
changeOrigin: true
})
);
// app.use(
// proxy("/fans/**", {
// target: "https://easy-mock.com/mock/5c0f31837214cf627b8d43f0/",
// changeOrigin: true
// })
// );
};

5.1 fetch的封装

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
import 'whatwg-fetch'
import 'es6-promise'

//导出get请求
export function fetchget(url) {
var result = fetch(url, {
credentials: 'include',
headers: {
'Accept': 'application/json, text/plain, */*'
}
});
return result.then(response => response.json());
}

// 发送 post 请求
export function fetchpost(url, paramsObj) {
var result = fetch(url, {
method: 'POST',
credentials: 'include',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/x-www-form-urlencoded'
},
body: obj2params(paramsObj)
});
return result.then(response => response.json());
}

// 将对象拼接成 key1=val1&key2=val2&key3=val3 的字符串形式
function obj2params(obj) {
var result = '';
var item;
for (item in obj) {
result += '&' + item + '=' + encodeURIComponent(obj[item]);
}

if (result) {
result = result.slice(1);
}

return result;
}

20.creat-react-app

Posted on 2019-11-29

1. React dev tools

将资料中的react-developer-tools-3.6.0 拖入到浏览器的扩展程序中

BP

2. creat-react-app创建项目

1
2
3
4
5
6
7
8
# 全局安装
npm install -g create-react-app

# 构建一个my-app的项目
npx create-react-app my-app

# 运行项目
yarn start /npm run start

BP

1
2
3
4
5
6
7
8
9
# react-scripts主要设计原理是将配置好的如 `Webpack,Babel,ESLint` ,合并到 `react-scripts` 这npm包中,用户就可以开箱即用

yarn run eject #反编译将react-scripts包反编辑成源代码(配置文件)
npm install #反编译之后要重新安装项目的依赖,否则项目运行不起来
yarn start #重新启动app

//修改yarn和npm的源 ★
yarn config set registry https://registry.npm.taobao.org/
npm config set registry https://registry.npm.taobao.org

3. creat-react-app基本配置

  1. 开启模块化 :

    只要给文件名命名为 xx.module.css xx.module.scss xx.module.less

  2. 开启sass:

    1
    yarn add node-sass

    然后创建button.scss或者button.module.scss,此时就可以使用scss文件了

  3. 在scss文件中引入其他文件

    ~代表nodel_modules

    1
    2
    @import '../styles/common.scss';
    @import '~bootstrap/dist/css/bootstrap.css';
  4. 开启less

    Yarn add less less-loader

    修改webpack.config.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
    //找到如下代码
    const cssRegex = /\.css$/;
    const cssModuleRegex = /\.module\.css$/;

    //修改为如下代码
    const cssRegex = /\.(css|less)$/;
    const cssModuleRegex = /\.module\.(css|less)$/;

    //找到这段代码
    test: cssRegex,
    exclude: cssModuleRegex,
    use: getStyleLoaders({
    importLoaders: 1,
    sourceMap: isEnvProduction && shouldUseSourceMap,
    }),
    // Don't consider CSS imports dead code even if the
    // containing package claims to have no side effects.
    // Remove this when webpack adds a warning or an error for this.
    // See https://github.com/webpack/webpack/issues/6571
    sideEffects: true

    //修改为如下代码
    test: cssRegex,
    exclude: cssModuleRegex,
    use: getStyleLoaders({
    importLoaders: 2,
    sourceMap: isEnvProduction && shouldUseSourceMap,
    },
    'less-loader'
    ),
    // Don't consider CSS imports dead code even if the
    // containing package claims to have no side effects.
    // Remove this when webpack adds a warning or an error for this.
    // See https://github.com/webpack/webpack/issues/6571
    sideEffects: true,
  5. 配置@路径

    修改webpack.config.js:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    alias: {
    // Support React Native Web
    // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
    'react-native': 'react-native-web',
    // Allows for better profiling with ReactDevTools
    ...(isEnvProductionProfile && {
    'react-dom$': 'react-dom/profiling',
    'scheduler/tracing': 'scheduler/tracing-profiling',
    }),
    ...(modules.webpackAliases || {}),
    '@':path.join(__dirname, '..', "src") ★
    },
  6. 环境变量的使用

    • 在项目根目录下创建.env文件,去写环境变量。注意:如果是自定义环境变量,需要以REACT_APP_开头

      1
      2
      REACT_APP_WEBSITE_NAME=myapp
      REACT_APP_PUBLIC_URL=localhost:3000
    • 使用环境变量

      1
      2
      3
      4
      5
      6
      7
      8
      9
      //在js代码中使用环境变量
      if (process.env.NODE_ENV !== 'production') {
      console.log("development")
      }
      {/*在组件中使用环境变量*/}
      <small>You are running this application in <b>{process.env.NODE_ENV}</b> mode.</small>
      <input type="text" defaultValue={process.env.REACT_APP_WEBSITE_NAME} />
      <!--在html页面中使用环境变量-->
      <title>%REACT_APP_WEBSITE_NAME%</title>
  7. 图片的使用

    • 在属性或者行业样式中使用图片

      1
      2
      import Logo from './assets/jjf.jpeg'
      <img src={Logo} alt="Logo" />;
    • 在css样式中使用图片 不需要引入,可以直接使用

      1
      2
      3
      4
      5
      6
      .logoDiv{
      width: 100px;
      height: 100px;
      background-image: url('./assets/jjf.jpeg');
      background-position: center;
      }
  8. 字体图标的使用

    • npm install font-awesome

    • 引入样式

      1
      import 'font-awesome/css/font-awesome.css'
    • 使用样式

      具体使用参照官网:http://www.fontawesome.com.cn/faicons/

      1
      <i class="fa fa-address-book" aria-hidden="true"></i>
  9. reactstrap的使用

    reactstrap:是一个基于boostrap的react组件库

    yarn add reactstrap [bootstrap@4.0.0]

    1
    2
    3
    4
    5
    6
    7
    import 'bootstrap/dist/css/bootstrap.css';
    import { Alert } from 'reactstrap';

    {/*使用reactstrap组件 */}
    <Alert color="primary">
    This is a primary alert — check it out!
    </Alert>

    具体用法:参照 http://reactstrap.github.io/components/modals/

4. AntDesign的使用

  1. 安装

    1
    yarn add antd
  2. 按需加载

    a. yarn add babel-plugin-import –dev

    b. 修改webpack.config.dev.js,将babelrc改成true

    c. 删除package.json中babel的配置

    d. 在项目根目录下创建一个 .babelrc文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    {
    "presets": [
    "react-app"
    ],
    "plugins": [
    [
    "import",
    {
    "libraryName": "antd",
    "style": "css"
    }
    ]
    ]
    }
  3. 使用antd

    1
    import { DatePicker } from 'antd';

    然后在页面中使用对应的组件就可以了

19.react-spring

Posted on 2019-11-27

1. react-spring基本使用

1.安装动画库

1
npm install react-spring

2.引入动画库

1
import { useSpring, animated, config } from 'react-spring'

3.声明改变透明度的动画

1
2
3
4
5
6
7
8
9
10
11
12
13
 const opacityAnimation = useSpring({
from: { opacity: 0 },
to: { opacity: 1 }
})
{/*把要动画的元素通过animated来修饰*/}
<animated.div style={opacityAnimation}>Demo1</animated.div>

//当我们要对组件进行动画的时候,我们需要使用animated方法把组件包裹起来,然后直接使用包裹后的组件名字
const AnimatedDonut4 = animated(Donut4)
return (
<AnimatedDonut4>
</AnimatedDonut4>
)

2. 动画的配置

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
//4.声明改变颜色的动画
/*
config的一些配置参数:
mass:影响速度以及过渡的效果。
tension:影响整体速度。
friction:控制阻力及其减速的速度。
clamp:是否应该瞬间过渡。

config.default { mass: 1, tension: 170, friction: 26 }
config.gentle { mass: 1, tension: 120, friction: 14 }
config.wobbly { mass: 1, tension: 180, friction: 12 }
config.stiff { mass: 1, tension: 210, friction: 20 }
config.slow { mass: 1, tension: 280, friction: 60 }
config.molasses { mass: 1, tension: 280, friction: 120 }
*/
const colorAnimation = useSpring({
from: { color: 'blue' },
color: 'red', //默认情况下 to可以省略
config: config.slow, //动画的配置
config: { duration: 2000,tension: 210 },
delay: 2000, //动画的延迟时间
reset:true, //从from状态过渡到to状态的时候有动画
reverse: true, //反转from和to的状态(reverse要生效,一定要先设置reset为true)
//动画开始的时候调用
onStart:()=>{
console.log("onStart")
},
//动画停止的时候调用
onRest:()=>{
console.log("onRest")
},
//每一帧动画都会调用onFrame,即使动画没有开始也会调用,会将动画的属性值作为参数入参
onFrame:(value)=>{
console.log("onFrame",value)
}
})

3. 添加多个动画属性和动画状态

1
2
3
4
5
6
7
8
9
10
11
//5.创建多个属性的动画
const multiAnimation = useSpring({
from: { opacity: 0, color: 'red' },
to: [
{ opacity: 1, color: '#ffaaee' },
{ opacity: 0.1, color: 'red' },
{ opacity: .5, color: '#008000' },
{ opacity: .8, color: 'black' }
],
config: { duration: 2000 },
});

//通过async和await来控制多个动画状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const animation = useSpring({
from: { left: '0%', top: '0%', width: '0%', height: '0%', background: 'lightgreen' },
to: async (next, cancel) => {
while (true) {
await next({ left: '0%', top: '0%', width: '100%', height: '100%',background: 'lightblue' })
await next({ height: '50%', background: 'lightgreen' })
await next({ width: '50%', left: '50%', background: 'lightgoldenrodyellow' })
await next({ top: '0%', height: '100%', background: 'lightpink' })
await next({ top: '50%', height: '50%', background: 'lightsalmon' })
await next({ width: '100%', left: '0%', background: 'lightcoral' })
await next({ width: '50%', background: 'lightseagreen' })
await next({ top: '0%', height: '100%', background: 'lightskyblue' })
await next({ width: '100%', background: 'lightslategrey' })
}
},
})

4. interpolate的使用

interpolate方法可以用来在动画执行的过程中,根据给定的起始、最终值,计算动画的每一步需要的值。

interpolate还有一个作用可以让我们模拟css的关键帧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const { o, xyz, color } = useSpring({
from: { o: 0, xyz: [0, 0, 0], color: 'red' },
to: { o: 1, xyz: [10, 20, 5], color: 'green' },
config: { duration: 2000 }
})

return (
<animated.div style={{
color,
opacity: o.interpolate(v => v),
transform: xyz.interpolate((x, y, z) => { return `translate3d(${x}px,${y}px,${z}px)` }),
//直接调用interpolate方法可以合并多个值,这边border使用多个值来设定样式
//第一个参数是要合并的值;第二个参数是一个箭头函数,用来返回指定样式
border: interpolate([o, color], (o, color) => { return `${o * 10}px solid ${color}` }),
//interpolate还可以来做关键帧动画,range对应动画的阶段,output对应每一个动画阶段的输出值*
padding: o.interpolate({ range: [0, 0.5, 1], output: [0, 1, 10] }).interpolate(o => `${o}%`),
borderColor: o.interpolate({ range: [0, 1], output: ['pink', 'yellow'] }),
//interpolate在做关键帧动画的时候,可以省略range和output
opacity: o.interpolate([0.1, 0.2, 0.6, 1], [1, 0.1, 0.5, 1])
}}>{/*如果要把动画属性渲染到页面,此时只能有一个元素*/}
{o.interpolate(v => v.toFixed(2))}
</animated.div>
)

注意点:interpolate只能被动画属性调用,普通字段是不可以调用interpolate方法

5. 使用state来控制动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//hook:允许我们在函数组件中使用state
const [flag, setFlag] = useState(0);

//这边所创建的animation的opacity的变化是通过一个flag的state来控制的
const animation = useSpring({
from: { opacity: 0 },
to: { opacity: flag ? 1 : 0 }
})

return (
<div>
<button onClick={()=>{setFlag(!flag)}}>点我切换透明度</button>
<animated.div onMouseOver={()=>{setFlag(true)}} onMouseOut={()=>{setFlag(false)}} style={animation}>透明度变了</animated.div>
</div>

6. 通过useSpring入参箭头函数来控制动画

1
2
3
4
5
6
7
8
9
//1.useSpring()接收箭头函数,此时会返回一个数组
//通过解构赋值获取到useSpring()函数返回的结果 animation表示动画属性,set可以用来修改动画属性,stop可以用来停止动画
const [animation, set, stop] = useSpring(() => ({ opacity: 0 }))

return (
<div>
<animated.div style={animation} onMouseOver={()=>{set({opacity: 1})}} onMouseOut={()=>{set({opacity: 0})}}>移入移出动画</animated.div>
</div>
)

7. useSprings动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
useSprings可以用来创建一组Spring,每个Spring都有自己的配置。

const items = [0.2, 0.4, 0.6, 0.8, 1.0];
//创建5个spring
//第一个spring的opacity=0.2
//第二个spring的opacity=0.4
//第三个spring的opacity=0.6
//第四个spring的opacity=0.8
//第五个spring的opacity=1.0
const springs = useSprings(5, items.map(item => ({ opacity: item,from:{opacity:0} })));

return (
springs.map((props,idx) => <animated.div key={idx} style={props}>item springs</animated.div>)
)

8. useTrail动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//useTrail可以用来创建一组Spring,每个Spring都有相同的配置,每个Spring跟随前一个Spring,在交叉的动画中使用

//这种写法可以
//const trail = useTrail(5, {opacity: 1})

//这种写法也可以
//创建5个spring,每个spring的配置都是一样的
const [trail, set, stop] = useTrail(5, () => ({ opacity: 1 }))

return (<div>
{trail.map((p, idx) => <animated.div key={idx} style={p} >哈哈</animated.div>)}
<button onClick={() => { set({ opacity: 0 }) }}>点我改变透明度</button>
</div>
)

9. useTransition动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
useTransition 可以进行过渡动画 (显示->隐藏)

//1.useTransition 可以对一组元素进行过渡动画
const [items, setItems] = useState([{ text: 'zhangsan', key: 1 }, { text: 'lisi', key: 2 }, { text: 'wangwu', key: 3 }])
//使用useTransition创建过渡动画
const transitions = useTransition(items, item => item.key, {
from: { transform: 'translate3d(0,-40px,0)' },
enter: { transform: 'translate3d(0,0px,0)' },
leave: { transform: 'translate3d(0,-40px,0)' },
})
return (<div>
<button onClick={() => { setItems(state => [{ text: 'zhaoliu', key: 12 }, ...state]) }}>增加</button>
{
transitions.map(({ item, props, key }) =>
<animated.div key={key} style={{ ...props, display: "inline-block", marginLeft: "20px" }}>{item.text}</animated.div>
)
}
</div>)
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
65
66
67
68
69
70
71
//2.使用useTransition还可以两个组件的切换动画、以及单个组件的切换动画
const [toggle, setToggle] = useState(false)

//创建一个transition动画
const transitions = useTransition(toggle, null, {
from: { position: 'absolute', opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 },
})

return (<div>
<button onClick={() => { setToggle(state => !state) }}>切换</button>
{
transitions.map(({ item, key, props }) =>
<React.Fragment key={key}>
{/* {item && <animated.div key={key} style={props}>切换单个组件</animated.div>} */}
{item ? <animated.div style={props}>组件1</animated.div> : <animated.div style={props}>组件2</animated.div>}
</React.Fragment>
)
}
</div>)


//使用useTransition切换路由

//App2.jsx
import React from 'react'
import About from '@/components2/About'
import Home from '@/components2/Home'
import Search from '@/components2/Search'

import { useTransition, animated } from 'react-spring'
import { HashRouter, Route, Link, Switch, useLocation } from 'react-router-dom'

const App2 = (props) => {
//1.获取当前的location位置,useLocation这个方法必须要在被HashRouter包裹的节点/子节点中才可以使用
const location = useLocation();
//2.创建一个过渡动画
const transitions = useTransition(location, location => location.pathname, {
from: { position: 'absolute', opacity: 0, transform: 'translate3d(0,-40px,0)' },
enter: { opacity: 1, transform: 'translate3d(0,0px,0)' },
leave: { opacity: 0, transform: 'translate3d(0,-40px,0)' },
})

return (
<div>
<Link to="/">来到首页</Link>
<Link to="/about">来到About</Link>
<Link to="/search">来到Search</Link>

{/*路由切换动画*/}
{transitions.map(({ item, props, key }) => (
<animated.div key={key} style={props}>
<Switch>
<Route path='/' exact component={Home} />
<Route path='/about' component={About} />
<Route path='/search' strict component={Search} />
</Switch>
</animated.div>
))}

</div>
)
}

export default App2;


//main.js
//这边必须要用HashRouter包裹App2,以便在App2中能使用useLocation方法
ReactDom.render(<HashRouter><App2></App2></HashRouter>,document.getElementById("app"))

10. useChain使用

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
useChain可以创建动画链,让多个动画按照顺序执行


import React, { useState,useRef } from 'react'
import { useSpring ,useTransition,useChain, animated } from 'react-spring'
import '@/styles/demo14.css'

const Demo14 = (props) => {
//transition动画的数据源
const data = [{ name: 1, css: "red" }, { name: 2, css: "blue" }, { name: 3, css: "yellow" }, { name: 4, css: "pink" }];
//定义一个open state用于控制窗口是否被打开
const [open, setOpen] = useState(false)

//useRef()方法可以创建一个引用,这边使用useRef()创建一个spring动画的引用
const springRef = useRef()
//创建Spring动画,作用是当open为true的时候,让父盒子变大
const { size, background } = useSpring({
ref: springRef,
from: { size: '30%', background: 'hotpink' },
to: { size: open ? '100%' : '30%', background: open ? 'white' : 'hotpink' }
})

//创建一个transRef的引用
const transRef = useRef()
//创建过渡动画(当父盒子打开后,若干子盒子呈现)
const transitions = useTransition(open ? data : [], item => item.name, {
ref: transRef,
unique: true, //动画进入和离开使用相同的key
trail: 400/data.length, //动画的延迟时间
from: { opacity: 0, transform: 'scale(0)' },
enter: { opacity: 1, transform: 'scale(1)' },
leave: { opacity: 0, transform: 'scale(0)' }
})

// 创建动画链 当open是true,先执行springRef动画;否则先执行transRef动画
// 当open为true,0~0.1执行springRef动画,0.1之后执行transRef动画
// 当open为false,0~0.6执行transRef动画,0.6之后执行springRef动画
useChain(open ? [springRef, transRef] : [transRef, springRef], [0, open ? 0.1 : 0.6])

return (
<animated.div className="parent" style={{ width: size, height: size, background }} onClick={() => setOpen(open => !open)}>
{transitions.map(({ item, key, props }) => (
<animated.div className="item" key={key} style={{ ...props, background: item.css }} />
))}
</animated.div>
)
}

export default Demo14;

14.react路由

Posted on 2019-11-26

1. react路由基本使用

1
2
3
4
5
6
7
8
//react-router的使用步骤:
//1.npm install react-router-dom

//2.引入HashRouter Route Link
//HashRouter 表示使用hash模式的路由
//Route 表示一个路由规则
//Link 表示点击跳转路由
import { HashRouter, Route, Link } from 'react-router-dom'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{/*3.配置路由规则  Route同时会占坑*/}
{/*路由默认会使用模糊匹配
比如:访问路径 /movie 能够匹配 / 和 /movie */}
{/* exact表示精确匹配 :访问路径需要和路由的路径完全一致才能匹配 */} ★
<Route path="/" component={Home} exact></Route>

{/* strict : 如果路由规则中的路径有/,则要求访问路径中也要有/才能匹配(只看/) */}
<Route path="/about/:name/:id" component={About} strict></Route>
<Route path="/movie" component={Movie}></Route>


{/*4.点击Link去访问路由*/}
<Link to="/">来到首页</Link>
<Link to="/about/zhangsan/13">来到关于我们</Link> <Link to="/movie">来到电影</Link>
<br/>
1
2
3
4
5
6
7
8
9
10
11
12
import { withRouter } from 'react-router-dom'

this.props.history.push("/")

export default withRouter (RTab);

<HashRouter>
<RNav></RNav>
<RouterMap></RouterMap>
<Button type='primary'>按钮</Button>
<RTab></RTab>
</HashRouter>

hashrouter 只建议套一个,一般套在最前面

2. switch和redirect和组件懒加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{/*当使用组件懒加载的时候,往往是和Suspense结合使用,fallback表示组件没有加载完毕的时候显示的信息*/}
<Suspense fallback={<div>Loading...</div>}>


{/*Switch表示路由互斥,我们使用Switch之后,只会匹配一个路由规则*/}
<Switch>


{/*我们需要给根路径的路由加上exact属性来精确匹配,否则所有的访问路径都会匹配Home组件*/}
<Route path="/" component={Home} exact></Route>
<Route path="/about/:name/:id" component={About} strict></Route>
<Route path="/movie" component={Movie}></Route>
<Route path="/search" component={Search}></Route>

{/*当没有匹配的路由的时候来到跟路由*/} 重定向
<Redirect to="/" />
</Switch>
</Suspense>

3.路由的传参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1.  /*通过params方式传参
获取参数:props.match.params
参数只能是字符串,而且参数会在浏览器地址栏中显示*/
<Link to="/about/zhangsan/13">来到关于我们-params方式传参</Link> <br />
//获取路由中的参数信息
return <div>About组件{props.match.params.id}{props.match.params.name}</div>

2. /*通过search方式传参
获取参数:props.location.search
参数只能是字符串,而且参数会在浏览器地址栏中显示*/
<Link to="/movie?name=xiaoming">来到电影-search方式传参</Link> <br />


3. /*通过query方式传参
获取参数:props.location.query
好处参数可以是对象,参数信息不会在浏览器的地址栏中显示,所以一刷新页面参数就没了*/
<Link to={{ pathname: '/movie', query: { name: 'sunny' } }}>来到电影2-query方式传参</Link> <br />


4. /*通过state方式传参
获取参数:props.location.state
好处参数可以是对象,参数信息不会在浏览器的地址栏中显示,
参数是加密的,所以一刷新页面参数就没了*/
<Link to={{ pathname: '/movie', state: { name: 'haha' } }}>来到电影3-state方式传参</Link> <br />

4.编程式导航

1
2
3
4
5
6
7
8
9
10
11
12
//使用Route包裹的组件会自动将location  history  match注入到组件的props中
//所以在组件中直接可以使用props.location props.history这些信息
// console.log(props)
<Route path="/movie" component={Movie}></Route>
props.history.push({pathname:"/search"})


//如果组件没有使用Route包裹,那么不能使用props的location history match这三个参数
//如果此时我们想要用这三个参数,我们可以将当前组件使用withRouter来包裹

//withRouter可以包装任何自定义组件,将react-router 的 history,location,match 三个对象传入
export default withRouter(MovieDetail);

5.ReactCSSTransitionGroup

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
//ReactCSSTransitionGroup 使用步骤
//1.通过npm install react-addons-css-transition-group 安装并引入
var ReactCSSTransitionGroup = require('react-addons-css-transition-group');


{/*2.给需要动画的元素包裹ReactCSSTransitionGroup,可以对一组元素进行过渡动画*/}
<ReactCSSTransitionGroup
component="ul"
transitionName="example"
transitionEnterTimeout={500}
transitionLeaveTimeout={300}
transitionAppear={true}
transitionAppearTimeout={500}>
{this.getItems()}
</ReactCSSTransitionGroup>



//3.写动画的样式 (入场动画 出场动画的样式)
import './demo1.css'

/*指定入场动画的开始状态*/
.example-enter {
opacity: 0.01;
}

/*指定入场动画的整个过程*/
.example-enter.example-enter-active {
opacity: 1;
transition: opacity 500ms ease-in;
}

/*指定出场动画的开始状态*/
.example-leave {
opacity: 1;
}

/*指定出场动画的整个过程*/
.example-leave.example-leave-active {
opacity: 0.01;
transition: opacity 300ms ease-in;
}

/*指定初始动画的样式*/
.example-appear {
opacity: 0.01;
}

/*指定初始动画的整个过程*/
.example-appear.example-appear-active {
opacity: 1;
transition: opacity .5s ease-in;

6. Hook

6.1 hook的基本使用

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { useState } from 'react'
function Demo1(props) {
//count代表state的变量
//setCount是一个function,如果我们要修改count变量,需要通过setCount来修改
let [count, setCount] = useState(0)
let [count2, setCount2] = useState(1)
let [count3, setCount3] = useState(2)

return (<div>
{count}
<button onClick={() => { setCount(++count) }}>点我修改count</button>
<br />
{count2}
<button onClick={() => { setCount2(++count2) }}>点我修改count2</button>
<br />
{count3}
<button onClick={() => { setCount3(++count3) }}>点我修改count3</button>
</div>)
}

export default Demo1;
  • 不要在循环,条件或嵌套函数中调用Hook,确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保
    Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确

6.2 effectHook

Effect Hook 可以让你在函数组件中执行副作用操作

副作用:函数内部的代码不对函数外部的代码产生任何影响

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
import React, { useState,useEffect  } from 'react'
import Child from './Child.jsx'

function Demo2(props) {
//count代表state的变量
//setCount是一个function,如果我们要修改count变量,需要通过setCount来修改
let [count, setCount] = useState(0)
let [count2, setCount2] = useState(1)

//useEffect函数可以来模拟class组件的中指定生命周期的钩子函数 componentDidMount,componentDidUpdate,componentWillUnmount

//相当于componentDidMount 、componentDidUpdate
useEffect(()=>{
console.log("xxxxxxxxxxx",count)
})

//相当于componentDidMount
useEffect(()=>{
console.log("yyyyyyyyy")
},[])

//当count2的值被改变之后执行当前的useEffect,其他值改变不会执行这个useEffect
useEffect(()=>{
console.log("zzzzzzzzzzzzz")
},[count2])

//useEffect函数中return的function会在组件重新渲染的时候以及组件卸载的时候执行
useEffect(()=>{
return ()=>{
console.log('hello Hook')
}
})

return (<div>
{count}
<button onClick={() => { setCount(++count) }}>点我修改count</button>
<br />
<button onClick={() => { setCount2(++count2) }}>点我修改count2</button>
<br />
<Child></Child>
</div>)
}

export default Demo2;

6.3 useContext 的使用

作用:可以向子孙组件发布数据

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
//1.在App.jsx
function App(){
return (<MyContext.Provider value={{name:"zhangsan"}}>
App
<Demo2></Demo2>
</MyContext.Provider>)
}



//2.Child.jsx
import React,{useContext} from 'react'
import MyContext from '@/context.js'

export default function(props){
//在子组件中获取父组件发布的数据
const contextValue = useContext(MyContext);
console.log(contextValue)
return (<div>xx</div>)
}


//定义context.js
import React from 'react'
const MyContext = React.createContext();
export default MyContext;

12.拓展-GraphQL简介

Posted on 2019-11-25

1.GraphQL

1.1 介绍

GraphQL 是一种 API 查询语言
GraphQL 是一种为 API 接口和查询已有数据运行时环境的查询语言. 它提供了一套完整的和易于理解的 API 接口数据描述, 给客户端权力去精准查询他们需要的数据, 而不用再去实现其他更多的代码, 使 API 接口开发变得更简单高效, 支持强大的开发者工具.

友情链接:

https://graphql.org

https://graphql.js.cool/

1.1.1 What is GraphQL?

正如副标题所说,GraphQL 是由 Facebook 创造的用于描述复杂数据模型的一种查询语言。这里查询语言所指的并不是常规意义上的类似 sql 语句的查询语言,而是一种用于前后端数据查询方式的规范。

1.1.2 Why using GraphQL?

当今客户端和服务端主要的交互方式有 2 种,分别是 REST 和 ad hoc 端点。GraphQL 官网指出了它们的不足之处主要在于:当需求或数据发生变化时,它们都需要建立新的接口来适应变化,而不断添加的接口,会造成服务器代码的不断增长,即使通过增加接口版本,也并不能够完全限制服务器代码的增长。

GraphQL 特性
  • 首先,它是声明式的。查询的结果格式由请求方(即客户端)决定而非响应方(即服务器端)决定,也就是说,一个 GraphQL 查询结果的返回是同客户端请求时的结构一样的,不多不少,不增不减。
  • 其次,它是可组合的。一个 GraphQL 的查询结构是一个有层次的字段集,它可以任意层次地进行嵌套或组合,也就是说它可以通过对字段进行组合、嵌套来满足需求。
  • 第三,它是强类型的。强类型保证,只有当一个 GraphQL 查询满足所设定的查询类型,那么查询的结果才会被执行。

BP

1.2 Node.js 环境简单用法

1.下载

GraphQL 规范的参考实现, 专为在 Node.js 环境中运行 GraphQL 而设计.

通过 命令行 执行 GraphQL.js hello world 脚本:

1
npm install graphql

2.创建 hello.js

然后执行 node hello.js.

hello.js源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
var { graphql, buildSchema } = require('graphql');

var schema = buildSchema(`
type Query {
hello: String
}
`);

var root = { hello: () => 'Hello world!' };

graphql(schema, '{ hello }', root).then((response) => {
console.log(response);
});

1.3 Express 运行

通过 Express 实现的 GraphQL 参考实现, 你可以联合 Express 运行或独立运行.

为了方便下面的步骤,我们在graphql文件夹中手动创建下面的目录结构,并安装指定的依赖

BP

运行一个 express-graphql 安装项目依赖

1
npm install express express-graphql graphql

1.index.js代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const express = require('express')
const expressGraphql = require('express-graphql')
const app = express()
// 配置路由
app.use('/graphql', expressGraphql(req => {
return {
schema: require('./schema'), // graphql相关代码主目录
graphiql: true // 是否开启可视化工具
// ... 此处还有很多参数,为了简化文章,此处就一一举出, 具体可以看 刚才开篇提到的 express文档,
// 也可以在文章末尾拉取项目demo进行查阅
}
}))
// 服务使用3000端口
app.listen(3000, () => {
console.log("graphql server is ok open http://localhost:3000/graphql");
});

2.graphql/schema.js代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const {
GraphQLSchema,
GraphQLObjectType
} = require('graphql')
// 规范写法,声明query(查询类型接口) 和 mutation(修改类型接口)
module.exports = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
description: '查询数据',
fields: () => ({
// 查询类型接口方法名称
fetchObjectData: require('./queries/fetchObjectData')
})
}),
mutation: new GraphQLObjectType({
name: 'Mutation',
description: '修改数据',
fields: () => ({
// 修改类型接口方法名称
updateData: require('./mutations/updateData')
})
})
})

3.graphql/queries/fetchObjectData.js代码

先在graphql/queries文件夹下创建fetchObjectData.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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
const {
GraphQLID,
GraphQLInt,
GraphQLFloat,
GraphQLString,
GraphQLBoolean,
GraphQLNonNull,
GraphQLObjectType
} = require('graphql')
// 定义接口返回的数据结构
const userType = new GraphQLObjectType({
name: 'userItem',
description: '用户信息',
fields: () => ({
id: {
type: GraphQLID,
description: '数据唯一标识'
},
username: {
type: GraphQLString,
description: '用户名'
},
age: {
type: GraphQLInt,
description: '年龄'
},
height: {
type: GraphQLFloat,
description: '身高'
},
isMarried: {
type: GraphQLBoolean,
description: '是否已婚',
deprecationReason: "这个字段现在不需要了"
}
})
})
// 定义接口
module.exports = {
type: userType,
description: 'object类型数据例子',
// 定义接口传参的数据结构
args: {
isReturn: {
type: new GraphQLNonNull(GraphQLBoolean),
description: '是否返回数据'
}
},
resolve: (root, params, context) => {
const { isReturn } = params
if (isReturn) {
// 返回的数据与前面定义的返回数据结构一致
return {
"id": "110000199811259999",
"username": "DongWenbin",
"age": 21,
"height": 182.5,
"isMarried": true
}
} else {
return null
}
}
}

4.graphql/mutations/updateData.js代码

先在graphql/mutations文件夹下创建updateData.js文件, 并填入以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const {
GraphQLInt
} = require('graphql')

let count = 0

module.exports = {
type: GraphQLInt, // 定义返回的数据类型
description: '修改例子',
args: { // 定义传参的数据结构
num: {
type: GraphQLInt,
description: '数量'
}
},
resolve: (root, params) => {
let { num } = params
count += num
return count
}
}

好了,到此为止,简单的GraphQL服务器就搭建好了,让我们来启动看看

1
node index.js  // 启动项目

然后我们在浏览器打开 http://localhost:3000/graphql 如下图所示

BP

我们可以看到页面分为3栏,左边的是调用api用的,中间是调用api返回的结果 右边实际上就是我们刚才定义接口相关的东西,也就是api文档。
我们在左边粘贴以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
query fetchObjectData {
fetchObjectData(
isReturn: true
) {
id
username
age
height
isMarried
}
}

mutation updateData {
updateData(
num: 2
)
}

1.4 语法规范

1、导入GraphQL.js及类型

graphql 无论在定义接口参数和接口返回结果时, 都需要先定义好其中所包含数据结构的类型, 这不难理解,可以理解为我们定义的就是数据模型,其中常用的类型如下。

1
2
3
4
5
6
7
8
9
10
const {
GraphQLList, // 数组列表
GraphQLObjectType, // 对象
GraphQLString, // 字符串
GraphQLInt, // int类型
GraphQLFloat, // float类型
GraphQLEnumType, // 枚举类型
GraphQLNonNull, // 非空类型
GraphQLSchema // schema(定义接口时使用)
} = require('graphql')

2、定义schema

schema实例中,一般规范为
query: 定义查询类的接口
mutation: 定义修改类的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query', // 查询实例名称
description: '查询数据', // 接口描述
fields: () => ({
// 查询类型接口方法名称
fetchDataApi1: require('./queries/fetchDataApi1'),
fetchDataApi2: require('./queries/fetchDataApi2'),
fetchDataApi3: require('./queries/fetchDataApi3'),
...
})
}),
mutation: new GraphQLObjectType({
name: 'Mutation',
description: '修改数据',
fields: () => ({
// 修改类型接口方法名称
updateDataApi1: require('./mutations/updateDataApi1'),
updateDataApi2: require('./mutations/updateDataApi2'),
...
})
})
})

3、接口方法定义

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
// 引用需要用到的数据类型
const {
GraphQLID,
GraphQLString,
GraphQLNonNull,
GraphQLObjectType
} = require('graphql')
// 第一部分 定义接口返回的数据结构
// 不难看出来,下面定义的是
/*
{
id
username
}
*/
const userType = new GraphQLObjectType({
name: 'userItem',
description: '用户信息',
fields: () => ({
id: {
type: GraphQLID,
description: '数据唯一标识'
},
username: {
type: GraphQLString,
description: '用户名'
}
})
})
// 第二部分 定义接口
module.exports = {
type: userType,
description: 'object类型数据例子',
// 定义接口传参的数据结构
args: {
isReturn: {
type: new GraphQLNonNull(GraphQLBoolean),
description: '是否返回数据'
}
},
resolve: (root, params, context) => {
const { isReturn } = params
// 返回的数据与前面定义的返回数据结构一致
return {
"id": "5bce2b8c7fde05hytsdsc12c",
"username": "Davis"
}
}
}

1.5 前后端交互(入门)

1、准备

1
npm i --save express express-graphql graphql cors

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
var express = require('express');
var graphqlHTTP = require('express-graphql');
const { buildSchema } = require('graphql');
const cors = require('cors'); // 用来解决跨域问题

// 创建 schema,需要注意到:
// 1. 感叹号 ! 代表 not-null
// 2. rollDice 接受参数
const schema = buildSchema(`
type Query {
username: String
age: Int!
}
`)
const root = {
username: () => {
return '董文斌'
},
age: () => {
return Math.ceil(Math.random() * 100)
},
}
const app = express();
app.use(cors());
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true
}))

app.listen(3300);
console.log('Running a GraphQL API server at http://localhost:3300/graphql')

3. 客户端代码index.html

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>graphql demo</title>
</head>

<body>
<button class="test">获取当前用户数据</button>
<p class="username"></p>
<p class="age"></p>
</body>
<script>
var test = document.querySelector('.test');
test.onclick = function () {
var username = document.querySelector('.username');
var age = document.querySelector('.age');
fetch('http://localhost:3300/graphql', {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
method: 'POST',
body: JSON.stringify({
query: `{
username,
age,
}`
}),
mode: 'cors' // no-cors, cors, *same-origin
})
.then(function (response) {
return response.json();
})
.then(function (res) {
console.log('返回结果', res);
username.innerHTML = `姓名:${res.data.username}`;
age.innerHTML = `年龄:${res.data.age}`
})
.catch(err => {
console.error('错误', err);
});
}
</script>

</html>

1.6 总结

目前前后端的结构大概如下图。后端通过 DAO 层与数据库连接,服务于主要处理业务逻辑的 Service 层,为 Controller 层提供数据源并产出 API;前端通过浏览器 URL 进行路由命中获取目标视图状态,而页面视图是由组件嵌套组成,每个组件维护着各自的组件级状态,一些稍微复杂的应用还会使用集中式状态管理的工具,比如 Vuex、Redux、Mobx 等。前后端只通过 API 来交流,这也是现在前后端分离开发的基础。

BP

如果使用 GraphQL,那么后端将不再产出 API,而是将 Controller 层维护为 Resolver,和前端约定一套 Schema,这个 Schema 将用来生成接口文档,前端直接通过 Schema 或生成的接口文档来进行自己期望的请求。

经过几年一线开发者的填坑,已经有一些不错的工具链可以使用于开发与生产,很多语言也提供了对 GraphQL 的支持,比如 Java/Nodejs、Java、PHP、Ruby、Python、Go、C# 等。

一些比较有名的公司比如 Twitter、IBM、Coursera、Airbnb、Facebook、Github、携程等,内部或外部 API 从 RESTful 转为了 GraphQL 风格,特别是 Github,它的 v4 版外部 API 只使用 GraphQL。据一位在 Twitter 工作的大佬说硅谷不少一线二线的公司都在想办法转到 GraphQL 上,但是同时也说了 GraphQL 还需要时间发展,因为将它使用到生产环境需要前后端大量的重构,这无疑需要高层的推动和决心。

正如尤雨溪所说,为什么 GraphQL 两三年前没有广泛使用起来呢,可能有下面两个原因:

  1. GraphQL 的 field resolve 如果按照 naive 的方式来写,每一个 field 都对数据库直接跑一个 query,会产生大量冗余 query,虽然网络层面的请求数被优化了,但数据库查询可能会成为性能瓶颈,这里面有很大的优化空间,但并不是那么容易做。FB 本身没有这个问题,因为他们内部数据库这一层也是抽象掉的,写 GraphQL 接口的人不需要顾虑 query 优化的问题。
  2. GraphQL 的利好主要是在于前端的开发效率,但落地却需要服务端的全力配合。如果是小公司或者整个公司都是全栈,那可能可以做,但在很多前后端分工比较明确的团队里,要推动 GraphQL 还是会遇到各种协作上的阻力。

大约可以概括为性能瓶颈和团队分工的原因,希望随着社区的发展,基础设施的完善,会渐渐有完善的解决方案提出,让广大前后端开发者们可以早日用上此利器。

11.react生命周期

Posted on 2019-11-25

1. React父子组件通信

1.1 父亲给孩子传递参数

1
2
3
4
return (<div>
App
<CMTItem name={'zhangsan'} {...obj}></CMTItem>
</div>)

1.2 孩子给父亲传递参数

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
> // 父亲中用来接收参数的函数 
> getChild = (obj) => {
> const id = this.state.CommentList.length + 1;
> console.log()
> obj.id = id;
> console.log("父亲接收数据的方法",obj);
> this.setState((state) =>({
> CommentList :[obj,...this.state.CommentList] ★★
> // 用来添加到数组,
> }))
> }
> // 父亲中的子组件的使用
> return (<div>
> App
> <CMTItem func={reload}></CMTItem>
> </div>)
>
> // 按钮绑定的函数用来传递数据
> postComment = () => {
> var obj = {}
> obj.user = this.refs.user.value
> obj.content = this.refs.content.value
> console.log(obj)
> if(this.refs.user.value&& this.refs.content.value){
> this.props.func(obj); ★
> }
> this.refs.user.value = this.refs.content.value =""
> }
>

1.3 Context的跨层传递

  • 使用要点:1.保证父子组件必须是类组件 ★
1
2
3
4
5
6
//这个方法用于给子孙组件传递数据 (数据的跨层传递)
getChildContext() {
return {
color: this.state.color
}
}
  • 定义向子孙组件传递的数据的类型
1
2
3
static childContextTypes = {
color: PropTypes.string
}
  • 子组件在使用父组件context数据的时候首先需要对父组件传递过来的数据做类型校验
1
2
3
4
render(){
//从this.context中获取跨层传递的数据
return (<div>这是CMTChild组件 {this.context.color} </div>)
}

2. react 组件的生命周期

2.1 对比 vue 的生命周期

1
2
3
4
5
6
7
8
9
Vue生命周期:
1. beforeCreate 组件创建之前,此时method和state还不可以使用
2. created 组件创建完毕,此时method和state可以使用
3. beforeMount 在内存中创建虚拟dom,此时页面还没有挂载
4. mounted 内存中虚拟dom已经渲染到页面,此时页面是最新的
5. beforeUpdate 页面更新之前,此时页面还是旧的
6. updated 页面更新之后,此时页面是最新的
7. beforeDestory 组件销毁之前
8. destroyed 组件销毁之后

2.2 react 生命周期的介绍

  • React组件的生命周期分为三个阶段:组件的创建阶段、组件的运行阶段、组件的销毁阶段
a. 创建阶段
1. static defaultProps
1
2
3
4
5
// 在封装一个组件的时候,组件内部肯定有一些数据是必须的,哪怕用户没有传递一些相关的启动参数,这时候组件内部尽量给自己提供一个默认值;
// 在 React 中使用静态的defaultProps属性来设置组件的默认属性值;
static defaultProps = {
initcount: 0 // 如果外界没有传递 initcount 那么自己初始化一个 数值为0
}
1
2
3
4
5
6
7
8
 import ReactTypes from "prop-types"
// 这是创建一个 静态的 propTypes 对象,可以对外界传递过来的属性做类型校验;
// 注意: 如果要为传递过来的属性做类型校验,必须安装 React 提供的 第三方包叫做 prop-types ;
static propTypes = {
// 使用 prop-types 包 来定义 initcount 为 number 类型
// isRequired 表示这个props属性是必须要传递的
initcount: ReactTypes.number.isRequired
}
  • 如果有些数据是必须的,必须要有初始值,但是没有传递,就可以使用 static
2.constructor
1
2
3
4
5
6
7
8
9
10
11
//2.生命周期第二个执行的是constructor 构造函数 (执行一次)
// 在创建组件对象的时候会自动调用构造函数
constructor(props) {
console.log("constructor")
super(props)
//在构造函数中指定组件的私有数据
this.state = {
count: props.initcount
}
this.divRef = React.createRef()
}
3. componentWillMount
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//3.当子组件接收到新的props会执行,在react16.4版本以后,setState修改state数据之后也会触发这个方法(执行多次)
// 在该方法中不能通过this.setSate来修改state数据,因为在staic修饰的方法中就没有this
//作用:将传递的props映射到state里面
//参数: nextProps 外部传递过来的props属性
// prevState 之前的state状态
static getDerivedStateFromProps(nextProps, prevState) {
console.log("getDerivedStateFromProps", nextProps, prevState)
//获取到父组件中传递过来的initcount值
const { initcount } = nextProps;
//当父组件中传递过来的initcount发生改变了
if (initcount != prevState.prevCount) {
//在方法中返回{count:initcount} 替换掉当前state中的 count值
return {
count: initcount,
prevCount:initcount
}
}
//如果父组件中传递过来的initcount没有变化,就返回一个空对象(此时不对当前的state做任何修改)
return null
}
4.render (){}
1
2
3
4
5
6
7
8
//4.创建虚拟dom,但是虚拟dom还没有挂载到页面 (执行多次)
// render函数中不能使用setState()来修改state的数据,因为会死循环
render() {
console.log("render", this.divRef.current) //null
return (<div ref={this.divRef}>
哈哈Counter {this.state.count}
</div>)
}
5.componentDidMount

虚拟 dom 已经挂载到页面,此时可以获取到最新的页面(执行一次)

1
2
3
4
//5.虚拟dom已经挂载到页面,此时可以获取到最新的页面 (执行一次)
componentDidMount() {
console.log("componentDidMount", this.divRef.current, this.state) //null
}
b. 运行阶段
1.static getDerivedStateFromProps
1
2
3
第一个会执行:static getDerivedStateFromProps
当父组件给子组件传递的props发生变化的时候,会执行该方法。
在react16.4版本以后,当前组件的state发生变化,会触发forceUpdate方法,然后还是会触发该方法
2.shouldComponentUpdate
1
2
3
4
5
6
7
8
9
10
// 第二个会执行: shouldComponentUpdate
// 在这个函数中,我们可以设置指定条件下去更新页面,指定条件下不更新页面。这个函数中入参props和state都是最新的
// 在该方法中不能通过this.setSate来修改state数据
shouldComponentUpdate(nextProps, nextState) {
console.log("shouldComponentUpdate", nextProps, nextState)
// 在 shouldComponentUpdate 中要求必须返回一个布尔值
// 在 shouldComponentUpdate 中,如果返回的值是 false,则不会继续执行后续的生命周期函数,而是直接退回到了运行中的状态,此时后续的render函数并没有被调用,因此页面不会被更新,但是组件的 state 状态,却被修改了;
// return nextState.count % 2 == 0 ? true : false;
return true;
}
3.render
1
//第三个会执行:render  此时会重新构建虚拟dom,但是新的虚拟dom还没有渲染到页面
4.getSnapshotBeforeUpdate
1
2
3
4
5
6
//第四个会执行getSnapshotBeforeUpdate 函数
// 在该方法中不能通过this.setSate来修改state数据
getSnapshotBeforeUpdate() {
console.log("getSnapshotBeforeUpdate")
return {}
}
5.componentDidUpdate ★★
1
2
3
4
5
6
  // 在该方法中不能通过this.setSate来修改state数据
//表示组件已经更新完毕 此时可以获取到最新的页面
componentDidUpdate() {
console.log("componentDidUpdate", this.state)
}
//只有在 componentDidMount 这个生命周期钩子函数中才可以写 setState, 其他钩子函数统统不可以写 ★
c . 销毁阶段
1. componentWillUnmount()
1
2
3
4
5
6
7
8
//很关键的,我们获取当前rootNode的scrollHeight,传到componentDidUpdate 的参数perScrollHeight
//在render方法调用之后,在componentDidUpdate之前执行,我们可以在这个方法中获取到元素的滚动位置的信息
//还可以在该方法中做一些样式的微调
getSnapshotBeforeUpdate() {
//console.log(this.rootNode.clientHeight)
//this.rootNode.style.backgroundColor = "red";
return this.rootNode.scrollHeight;
}
123

Dong Wenbin

21 posts
© 2020 Dong Wenbin
Powered by Hexo
|
Theme — NexT.Muse v5.1.4