wangbo123855842 / Learning

15 stars 2 forks source link

React 技术栈 #17

Open wangbo123855842 opened 4 years ago

wangbo123855842 commented 4 years ago
スクリーンショット 2020-05-09 14 41 08

React

React关联的技术栈

JavaScript

スクリーンショット 2020-05-11 10 03 09

JavaScript 面向对象

原型编程

基于原型的编程不是面向对象编程中体现的风格,且行为重用(在基于类的语言中也称为继承)是通过装饰它作为原型的现有对象的过程实现的。这种模式也被称为弱类化,原型化,或基于实例的编程。

命名空间

Javascript中的普通对象和命名空间在语言层面上没有区别。 比如,声明一个全局命名空间

var MYAPP = MYAPP || {};

MYAPP.commonMethod = {
  regExForName: "", 
  regExForPhone: "", 
  validateName: function(name){ },
  validatePhoneNo: function(phoneNo){ }
}

// 对象和方法一起申明
MYAPP.event = {
   addListener: function(el, type, fn) { },
   removeListener: function(el, type, fn) { },
   getEvent: function(e) { }
   // 还可以添加其他的属性和方法
}

//使用addListener方法的写法:
MYAPP.event.addListener("yourel", "type", callback);

自定义对象 类

JavaScript是一种基于原型的语言,它没类的声明语句(ES6 开始提供 Class声明) JavaScript可用方法作类。定义一个类跟定义一个函数一样简单。 在下面的例子中,我们定义了一个新类Person。

function Person() { } 
或者
var Person = function(){ }

对象(类的实例)

我们使用 new obj 创建对象 obj 的新实例, 将结果(obj 类型)赋值给一个变量方便稍后调用。

function Person() { ...... }
var person1 = new Person();
var person2 = new Person();

新增的 Object.create 方法来创建对象

const person = {
  isHuman: false,
  printIntroduction: function() {
    console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
  }
};
const me = Object.create(person);

构造器 对象属性

function Person(firstName) {
  this.firstName = firstName;
  alert('Person instantiated');
}
var person1 = new Person('Alice');
var person2 = new Person('Bob');

可以使用 关键字 this调用类中的属性, this是对当前对象的引用。

继承 (prototype继承)

创建一个或多个类的专门版本类方式称为继承(Javascript只支持单继承)。 创建的专门版本的类通常叫做子类,另外的类通常叫做父类。 在Javascript中,继承通过赋予子类一个父类的实例并专门化子类来实现。 在下面的例子中, 我们定义了 Student类作为 Person类的子类. 之后我们重定义了sayHello() 方法并添加了 sayGoodBye() 方法

// 定义Person构造器
function Person(firstName) {
  this.firstName = firstName;
}

// 在Person.prototype中加入方法
Person.prototype.walk = function(){
  alert("I am walking!");
};
Person.prototype.sayHello = function(){
  alert("Hello, I'm " + this.firstName);
};

// 定义Student构造器
function Student(firstName, subject) {
  // 调用父类构造器, 确保(使用Function#call)"this" 在调用过程中设置正确
  Person.call(this, firstName);

  // 初始化Student类特有属性
  this.subject = subject;
};

// 建立一个由Person.prototype继承而来的Student.prototype对象.
Student.prototype = Object.create(Person.prototype); 

// 设置"constructor" 属性指向Student
Student.prototype.constructor = Student;

// 更换"sayHello" 方法
Student.prototype.sayHello = function(){
  console.log("Hello, I'm " + this.firstName + ". I'm studying " + this.subject + ".");
};

// 加入"sayGoodBye" 方法
Student.prototype.sayGoodBye = function(){
  console.log("Goodbye!");
};

// 测试实例:
var student1 = new Student("Janet", "Applied Physics");
student1.sayHello();   // "Hello, I'm Janet. I'm studying Applied Physics."
student1.walk();       // "I am walking!"
student1.sayGoodBye(); // "Goodbye!"

// Check that instanceof works correctly
console.log(student1 instanceof Person);  // true 
console.log(student1 instanceof Student); // true

JavaScript 中的值和类型

JavaScript提供两种数据类型: 基本数据类型和引用数据类型

基本数据类型有

JavaScript 中比较对象

对于两个非原始值,比如两个对象(包括函数和数组),== 和 === 比较都只是检查它们的引用是否匹配,并不会检查实际引用的内容

默认情况下,数组将被强制转型成字符串,并使用逗号将数组的所有元素连接起来。所以,两个具有相同内容的数组进行 == 比较时不会相等

var a = [1,2,3];
var b = [1,2,3];
var c = "1,2,3";
a == c; // true
b == c; // true
a == b; // false

原型设计模式

原型模式可用于创建新对象,但它创建的不是非初始化的对象,而是使用原型对象的值进行初始化的对象。原型模式也称为属性模式。 原型模式在初始化业务对象时非常有用,业务对象的值与数据库中的默认值相匹配。原型对象中的默认值被复制到新创建的业务对象中。 经典的编程语言很少使用原型模式,但作为原型语言的 JavaScript 在构造新对象及其原型时使用了这个模式

Javascript 匿名函数

有名函数

function  myFun( a,b ){
    console.info( a+b );
}
myFun( 10 , 30 );  

匿名函数: 有关键词 function, 有小括号,有大括号,就是没有函数名。

function(a,b){
    console.info( a+b );
}

执行匿名函数 方式1:把它放进一个变量里,这个变量就相当于一个函数名了

let  myFun = function( a,b ){
    console.info( a+b);
};
myFun( 10,30 );

方式2:干脆不要名字,直接执行

(function(a,b){
    console.info( a+b );
})(10,30);
或者
(function(a,b){
    console.info( a+b );
}(10,30));

方式3:作为另一个函数的参数

function myFun(fn){
    fn();
}
myFun( function(){
    console.info("这个匿名函数是个参数");
});

Javascript 闭包

首先看一下变量的作用域 变量的作用域无非就是两种:全局变量和局部变量。 函数内部可以直接读取全局变量。

var n=999;
function f1(){
    alert(n);
}
f1(); // 999

在函数外部自然无法读取函数内的局部变量。

    var n=999;
}
alert(n);    // error

这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量

function f1(){
    n=999;
}
f1();
alert(n); // 999

如何从外部读取局部变量?

出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。 那就是在函数的内部,再定义一个函数

function f1(){
    var n=999;
    function f2(){
        alert(n);
    }
    return f2;
}
var result=f1();
result(); // 999

函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。 既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!

闭包的概念

我的理解是,闭包就是能够读取其他函数内部变量的函数。 由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成定义在一个函数内部的函数。 所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁

闭包的用途

它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中

function f1(){
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
        alert(n);
    }
    return f2;
}

var result=f1();
result(); // 999

nAdd();
result(); // 1000

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制回收。

这段代码中另一个值得注意的地方,就是nAdd=function(){n+=1}这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

使用闭包的注意点

  1. 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
  2. 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

Less

スクリーンショット 2020-05-11 10 05 03

Less是一门css预处理语言,他扩展了css语言,增加了变量、Mixin、函数等特性,使css更容易维护和扩展。他不是一个直接使用的语言,而是一个生成css的语言。Less可以运行在Node或浏览器端。

可以把less理解成 动态样式语言

普通变量

less 以@开头定义变量,并且使用时直接键入@名称

@color:#333;
Div{
  Background:@color;
}

Less其变量只能定义一次,不能重复定义,否则后面的会类似与js的变量提升,覆盖前面的变量。 使用变量设置css,方便代码的维护。

选择器变量

选择器变量让选择器变成一个动态的。 作为选择器的变量在使用的时候需要添加大括号 {},变量的前面可以添加选择操作符。

@mySelector: #wrap;
@Wrap: wrap;

@{mySelector}{ // 变量名 必须使用大括号包上
    color: #999;
    width: 50%;
}
.@{Wrap}{
    color:#ccc;
}
#@{Wrap}{
    color:#666;
}

生成的CSS

#wrap{
    color: #999;
    width: 50%;
}
.wrap{
    color:#ccc;
}
#wrap{
    color:#666;
}

属性变量

@borderStyle: border-style;
@Soild:solid;
#wrap{
    @{borderStyle}: @Soild;//变量名 必须使用大括号包裹
}

生成的CSS

#wrap{
    border-style:solid;
}

Url变量

项目结构改变时,可以方便我们修改,目的是为了方便项目的维护。

@images: "../img"; // 需要加引号
body {
    background: url("@{images}/dog.png");//变量名 必须使用大括号包裹
}

生成的CSS

body {
    background: url("../img/dog.png");
}

声明变量

@Rules:{
    width: 200px;
    height: 200px;
    border: solid 1px red;
};
#con{
    @Rules();
}

变量运算

@width:300px;
@color:#222;

#wrap{
    width:@width-20;
    height:@width-20*5;
    margin:(@width-20)*5;
    color:@color*2;
    background-color:@color + #111;
}

用变量的定义变量

解析的顺序是从后向前逐层解析

@fnord:  "I am fnord.";
@var:    "fnord";

#wrap::after{
    content: @@var; // 将@var替换为其值 content:@fnord;
}

生成的 CSS

#wrap::after{
    content: "I am fnord.";
}

媒体查询

之前媒体查询的格式是, 先分屏然后在每一个媒体查询中设置样式。

@media only screen and {max-width:1200px;}{
  Div{}
}
@media only screen and  {min-width:992px;}{
  Div{}
}
@media only screen and {min-width:768px}{
  Div{}
}

less写法

#main{
    @media screen{
        @media (max-width:768px){
            width:100px;
        }
    }
    @media tv {
            width:2000px;
        }
    }
}

生成的 CSS

@media screen and (max-width:768px){
    #main{
        width:100px;
    }
}
@media tv{
    #main{
        width:2000px;
    }
}

参数

Less可以传递参数

.setSize(@width_size,@height_size){
    width:@width_size;
    height:@height_size;
}

Less 可以使用默认参数,如果没有传参数,那么将使用默认参数。 @arguments 犹如 JS 中的 arguments 指代的是全部参数。传的参数中必须带着单位。

.border(@a:10px,@b:50px,@c:30px,@color:#000){
    border:solid 1px @color;
    box-shadow: @arguments;// 指代的是 全部参数
}
#main{
    .border(0px,5px,30px,red);//必须带着单位
}
#wrap{
    .border(0px);
}

条件语句

Less没有if / else 但是less具有一个when,and,not与“,”语法。

#card{
    // and 运算符 ,相当于 与运算 &&,必须条件全部符合才会执行
    .border(@width,@color,@style) when (@width>100px) and(@color=#999){
        border:@style @color @width;
    }
    // not 运算符,相当于 非运算 !,条件为 不符合才会执行
    .background(@color) when not (@color>=#222){
        background:@color;
    }
    // , 逗号分隔符:相当于 或运算 ||,只要有一个符合条件就会执行
    .font(@size:20px) when (@size>50px) , (@size<100px){
        font-size: @size;
    }
}
#main{
    #card > .border(200px,#999,solid);
    #card .background(#111);
    #card > .font(40px);
}

生成后的

#main{
    border:solid #999 200px;
    background:#111;
    font-size:40px;
}

继承(扩展)

extend是less的一个伪类。它可以继承所匹配声明中的全部样式 & 符号,表示的是上1级选择器的名字。

.animation{
    transition: all .3s ease-out;
    .hide{
        transform:scale(0);
    }
}
#main{
    &:extend(.animation);
}
#con{
    &:extend(.animation .hide);
}

生成后的 CSS

.animation,#main{
    transition: all .3s ease-out;
}
.animation .hide , #con{
    transform:scale(0);
}

导入

在less文件中可以引入其他的less文件。使用关键字import。 导入less文件,可以省略后缀。

import “index.less”;
import “index”;

Less中使用JavaScript

less本身是使用js实现的,所以在less中可以使用js。 Js的代码写在字符串模板里

#wrap{
    width: ~"`Math.round(Math.random() * 100)`px";
    height: ~"`window.innerHeight`px";
}

生成后的 CSS

#wrap{
    width: 随机值(0~100)px;
    height: 743px;//由电脑而异
}

Less的函数

Less 有很多的内置的函数 http://lesscss.cn/functions/#functions-overview 比如

image-width("file.png");

@list: "banana", "tomato", "potato", "peach";
n: length(@list);

@list: "3px", "42px", "1px", "16px";
min(@list);

ECMAScript 6

スクリーンショット 2020-05-11 13 10 11

let

let与var一样是用来声明变量的,与var的区别是 let所声明的变量,只在let所在的代码块内有效

for(let i=0 ; i<5; i++) {
console.log(i);//输出1,2,3,4
}
console.log(i);//i is not defined

作用域

通过let定义的变量,作用域是在定义它的块级代码以及其中包括的子块中,并且无法在全局作用域添加变量。 通过var定义的变量,作用域为包括它的函数作用域或者全局作用域

var的作用域

function varTest() {
  var x = 1;
  if (true) {
    var x = 2;  // same variable!
    console.log(x);  // 2
  }
  console.log(x);  // 2
}

let的作用域

function letTest() {
  let x = 1;
  if (true) {
    let x = 2;  // different variable
    console.log(x);  // 2
  }
  console.log(x);  // 1
}

let 无法在全局作用域中定义变量

var x = 'global';
let y = 'global';
console.log(this.x); // "global"
console.log(this.y); // undefined

let 也非常适合 for 循环,在循环中 i 的值只在循环语句中生效,在外边取不到的

for(var i=0;i<10;i++) {
    //...
}
console.log(i); // 10
for(let j=0;j<10;j++) {
    //...
}
console.log(j); // j is not defined

重复声明

另外let不允许在同一作用域下声明已经存在的变量,即在同一作用域下不可以声明两个变量名相同的变量,会直接报错,但是var不会,可见let比var更加规范化 let未先声明变量就用变量会编译出错,但是var不会,会输出undefined

// 报错
(function () {
  let a = 10;
  var a = 1;
  console.log(a)
})()
// 报错
(function () {
  let a = 10;
  let a = 1;
  console.log(a)
})()
// OK
(function () {
  var a = 10;
  var a = 1;
  console.log(a) // 1
})()

const

const声明的是常量。一旦声明,常量的值就不能改变。

const PI = Math.PI
PI = 23 // Module build failed: SyntaxError: /es6/app.js: "PI" is read-only

当我们尝试去改变用const声明的常量时,浏览器就会报错。

下面的定义,试问这时候如果输出obj1 和 num的值,分别是多少呢?

let obj = {'num1' : 20, 'num2' : 30}
const obj1 = obj
const num = obj.num1 
obj.num1 = 40

答案

obj1
 {'num1' : 40, 'num2' : 30}
num
20

也就是说 const定义的对象,当对象改变了之后,const定义的值也会跟着改变。 cosnt定义的变量是一个对象的一个属性值,但是当对象属性值改变了以后,const定义的这个值并不会改变。

那么这是为了什么呢? 在计算机中,常量是放在栈中的,而对象是放在堆中的。对于对象赋值,const指向的仅仅是他的地址,cosnt仅仅是保证这个地址不改变,至于地址对应的数据,是可以进行改变的。 而如果定义一个简单的数据类型,那这个数据他本身就是存在栈中的,所以不可以改变。

class

class, extends, super 这三个特性涉及了ES5中最令人头疼的的几个部分:原型、构造函数,继承... ES6提供了更接近传统语言的写法,引入了Class(类)这个概念。新的class写法让对象原型的写法更加清晰、更像面向对象编程的语法,也更加通俗易懂。

class Animal {
    constructor(){
        this.type = 'animal'
    }
    says(say){
        console.log(this.type + ' says ' + say)
    }
}

let animal = new Animal()
animal.says('hello') //animal says hello

class Cat extends Animal {
    constructor(){
        super()
        this.type = 'cat'
    }
}

let cat = new Cat()
cat.says('hello') //cat says hello
  1. class定义了一个类
  2. constructor方法,这就是构造方法
  3. this关键字则代表实例对象

Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。上面定义了一个Cat类,该类通过extends关键字,继承了Animal类的所有属性和方法。

super关键字,它指代父类的实例(即父类的this对象)。子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。 ES6的继承机制,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this

箭头函数

ES6最常用的一个新特性了,用它来写function比原来的写法要简洁清晰很多

function(i){ return i + 1; } // ES5
(i) => i + 1 // ES6

如果方程比较复杂,则需要用 {} 把代码包起来

function(x, y) {
    x++;
    y--;
    return x + y;
}
(x, y) => {x++; y--; return x+y}

长期以来,JavaScript语言的this对象一直是一个令人头痛的问题,在对象方法中使用this,必须非常小心。例如

class Animal {
    constructor(){
        this.type = 'animal'
    }
    says(say){
        setTimeout(function(){
            console.log(this.type + ' says ' + say)
        }, 1000)
    }
}

var animal = new Animal()
animal.says('hi')  // undefined says hi

运行上面的代码会报错,这是因为

在Javascript里面,this指针代表的是执行当前代码的对象的所有者

传统的解决方法有两种 第一种是将this传给self,再用self来指代this

says(say){
      var self = this;
      setTimeout(function(){
          console.log(self.type + ' says ' + say)
      }, 1000)

第二种方法是用bind(this)

 says(say){
      setTimeout(function(){
          console.log(this.type + ' says ' + say)
      }.bind(this), 1000)

但现在我们有了箭头函数,就不需要这么麻烦了

class Animal {
    constructor(){
        this.type = 'animal'
    }
    says(say){
        setTimeout( () => {
            console.log(this.type + ' says ' + say)
        }, 1000)
    }
}
 var animal = new Animal()
 animal.says('hi')  //animal says hi

当我们使用箭头函数时,函数体内的this对象,就是定义时所在的对象而不是使用时所在的对象。并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,它的this是继承外面的,因此内部的this就是外层代码块的this。

箭头函数最直观的三个特点

解构

ES6 中允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构 同名变量解构赋值

let node = {
        type : 'identifier',
        name : 'foo'
};
let {type,name} = node;

不同变量解构赋值

let node = {
        type : 'identifier',
        name : 'foo'
};
let {type:localType,name:localName} = node;

default

default很简单,意思就是默认值。 下面的例子,调用animal()方法时忘了传参数,传统的做法就是加上这一句type = type || 'cat'来指定默认值。

// ES5
function animal(type){
    type = type || 'cat'  
    console.log(type)
}
animal()

如果用ES6我们而已直接这么写

function animal(type = 'cat'){
    console.log(type)
}
animal()

扩展运算符 ...

对象中的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中

let obj1 = { a: 1, b: 2};
let obj2 = { ...obj1, b: '2-edited'};
console.log(obj1); // {a: 1, b: 2}
console.log(obj2); //  {a: 1, b: "2-edited"}

扩展运算符可以与解构赋值结合起来,用于生成数组

const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest  // [2, 3, 4, 5]

import 和 export

// 全部导入
import people from './example'

//该模块的所有导出都会作为对象的属性存在
import * as example from "./example.js"
console.log(example.name)
console.log(example.age)
console.log(example.getName())

//导入部分
import {name, age} from './example'

// 导出默认, 有且只有一个默认
export default App

// 部分导出
export class App extend Component {};

export 加default和不加的区别

// 第一组
export default function crc32() {
  ...
}
import crc32 from 'crc32'; 

// 第二组
export function crc32() { 
  ...
};
import { crc32 } from 'crc32'; 

export default也可以用来输出类

// MyClass.js
export default class { ... }
// main.js
import MyClass from 'MyClass';

Promise

Promise对象可以理解为一次执行的异步操作,使用promise对象之后可以使用一种链式调用的方式来组织代码;让代码更加的直观

比如我使用ajax发一个A请求后,成功后拿到数据,我们需要把数据传给B请求;那么我们需要如下编写代码

$.ajax({
      url: '',
      dataType:'json',
      success: function(data) {  // 获取data数据 传给下一个请求
          var id = data.id;
          $.ajax({
              url:'',
              data:{"id":id},
              success:function(){  // .....
             }
          });
      }
});

这种情况下,代码不够直观。

Promise 基本用法

创建一个 Promise

const promise = new Promise((resolve, reject) => {
    // do something here ...
    if (success) {
        resolve(value); // fulfilled
    } else {
        reject(error); // rejected
    }
});

该构造函数接收两个函数作为参数,分别是resolvereject

然后,我们通过then方法,分别指定resolved状态和rejected状态的回调函数

promise.then(function(value) {
      // success
}, function(error) {
      // failure
});

then方法可以接收两个回调函数作为参数,第一个回调函数就是fulfilled状态时调用;第二个回调函数就是rejected时调用。这边的第二个参数是可选的,不一定要提供

Promise.all

var p1 = new Promise((resolve, reject) => { 
  setTimeout(resolve, 1000, 'one'); 
}); 
var p2 = new Promise((resolve, reject) => { 
  setTimeout(resolve, 2000, 'two'); 
});
var p3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 3000, 'three');
});
var p4 = new Promise((resolve, reject) => {
  reject('p4 reject!');
});
var p5 = new Promise((resolve, reject) => {
  reject('p5 reject!');
});

Promise.all([p1, p2, p3, p4, p5]).then(values => { 
  console.log(values);
}, reason => {
  console.log(reason)
});

// p4 reject!

传入的参数中任意一个promise返回失败时,那么整体立即返回失败,返回的错误信息是第一个失败的promise结果

Promise.all([GET(adminUrl), GET(contentUrl)])
        .then((res) => {
          const { is_creator: isCreator } = res[0].data
          const { total_count: count, items: data } = res[1].data
          this.setState({
            isCreator: parseInt(isCreator, 10) === 1,
            count,
            data,
            loading: false,
          })
        })
        .catch((e) => {
          console.warn(e)
        })

Promise.prototype. catch()

推荐使用catch的写法 不要在then方法中定义rejected状态的回调函数;这是因为使用catch还可以捕获在then方法执行中存在的错误

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { 
    // success
  })
  .catch(function(err) {
    // error
  });

Promise.prototype. finally()

返回一个Promsie。是指,在上一轮 promise 运行结束后,无论fulfilled还是 rejected,都会执行指定的回调函数。 该方法适合,无论结果如何都要进行的操作,例如清除数据

Promise.all 是并行处理,全部处理完成之后,到then里面处理

Promise.reduce

Promise.reduce 方法是一个顺序执行 Promise 的方法,所谓顺序执行,其实就是从左到右按顺序去创建 Promise ,并且始终只有一个 Promise 在运行。

function a(n, timeout) {
  return function() {
    return new Promise(function(resolve, reject) {
      setTimeout(function() {
        console.log(n);
        resolve(n);
      }, timeout);
    });
  };
}

var promises = [
  a(1, 300),
  a(2, 200),
  a(3, 100)
];

promises.reduce(function(prev, curr, index, array) {
  return prev.then(curr);
}, Promise.resolve());

async await

在async/await之前,我们有三种方式写异步代码

但是,这三种写起来都不够优雅,ES7做了优化改进,async/await应运而生,async/await相比较Promise 对象then 函数的嵌套,与 Generator 执行的繁琐(需要借助co才能自动执行,否则得手动调用next() ), Async/Await 可以让你轻松写出同步风格的代码同时又拥有异步机制,更加简洁,逻辑更加清晰。

async/await是一个用同步思维解决异步问题的方案(等结果出来之后,代码才会继续往下执行)

可以通过多层 async function 的同步写法代替传统的callback嵌套

async 函数返回的是一个 Promise 对象

function takeLongTime() {
    return new Promise(resolve => {
        setTimeout(() => resolve("long_time_value"), 1000);
    });
}

takeLongTime().then(v => {
    console.log("got", v);
});

改写成 async await 的方式

function takeLongTime() {
    return new Promise(resolve => {
        // 异步操作
        setTimeout(() => resolve("long_time_value"), 1000);
    });
}

async function test() {
    const v = await takeLongTime();
    console.log(v);
}

test();

async 函数返回的是一个 Promise 对象,如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象await 只会出现在 async 函数中,我们使用 async/await 时,几乎不需要 .then,因为 await 为我们处理等待;但是在代码的顶层,当我们在 async 函数的外部时,我们在语法上是不能使用 await 的,所以通常添加 .then/catch 去处理最终结果或者 error

单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了 假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。我们仍然用 setTimeout 来模拟异步操作

function takeLongTime(n) {
    return new Promise(resolve => {
        setTimeout(() => resolve(n + 200), n);
    });
}

function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}

function step2(n) {
    console.log(`step2 with ${n}`);
    return takeLongTime(n);
}

function step3(n) {
    console.log(`step3 with ${n}`);
    return takeLongTime(n);
}

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step3(time3))
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}

doIt();

// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms

如果用 async/await 来实现呢,会是这样

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time2);
    const result = await step3(time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}

doIt();

Object.assign

基本用法

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)

const target = { a: 1 };

const source1 = { b: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。 注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

Babel

スクリーンショット 2020-05-12 13 09 51

babel是一个JS编译器,用来转换最新的JS语法,比如把ES6, ES7等语法转化成ES5语法,从而能够在大部分浏览器中运行。像箭头函数,就可以做转换。babel在执行过程中,分三步:先分析(parsing)、再转化、最后生成代码。

但babel只转换语法的话,一些最新的api是不转化的,比如Object.assign, Promise等。所以babel还提供了很多插件,也就是babel-ployfill。安装后,即可支持浏览器运行。 babel还可以转换JSX语法,对React支持比较好

一般与webpack结合使用 (babel-loader)

NPM / Yarn

スクリーンショット 2020-05-12 13 52 28

NPM

npm 不需要单独安装。在安装 Node 的时候,会连带一起安装 npm 。但是,Node 附带的 npm 可能不 是最新版本,最后用下面的命令,更新到最新版本

sudo npm install npm@latest -g

npm init

npm init 用来初始化生成一个新的 package.json 文件。它会向用户提问一系列问题,如果你觉得不用修改默认配置,一路回车就可以了。 如果使用了 -f(代表 force)、-y(代表 yes),则跳过提问阶段,直接生成一个新的 package.json 文件。

npm init -y

npm search

npm search 命令用于搜索 npm 仓库,它后面可以跟字符串,也可以跟正则表达式。

npm search <搜索词>

npm list

npm list 命令以树形结构列出当前项目安装的所有模块,以及它们依赖的模块。

npm list

npm install

Node模块采用npm install命令安装。 每个模块可以全局安装,也可以本地安装。“全局安装”指的是将一个模块安装到系统目录中,各个项目都可以调用。一般来说,全局安装只适用于工具模块,比如eslint和gulp。“本地安装”指的是将一个模块下载到当前项目的node_modules子目录,然后只有在项目目录之中,才能调用这个模块。

# 本地安装
npm install <package name>

# 全局安装
sudo npm install -global <package name>
sudo npm install -g <package name>

install命令总是安装模块的最新版本,如果要安装模块的特定版本,可以在模块名后面加上@和版本号。

npm install sax@latest
npm install sax@0.1.1
npm install sax@">=0.1.0 <0.2.0"

install命令可以使用不同参数,指定所安装的模块属于哪一种性质的依赖关系,即出现在packages.json文件的哪一项中。

–save:模块名将被添加到dependencies,可以简化为参数-S。
–save-dev: 模块名将被添加到devDependencies,可以简化为参数-D。

比如

npm install sax --save
npm install node-tap --save-dev
# 或者
npm install sax -S
npm install node-tap -D

npm install默认会安装dependencies字段和devDependencies字段中的所有模块, 如果使用--production参数,可以只安装dependencies字段的模块。

npm install --production
# 或者
NODE_ENV=production npm install

一旦安装了某个模块,就可以在代码中用require命令加载这个模块。

var backbone = require('backbone')
console.log(backbone.VERSION)

npm update

npm update命令可以更新本地安装的模块。

# 升级当前项目的指定模块
npm update [package name]

# 升级全局安装的模块
npm update -global [package name]

使用-S或--save参数,可以在安装的时候更新package.json里面模块的版本号。

// 更新之前的package.json
dependencies: {
  dep1: "^1.1.1"
}
// 更新之后的package.json
dependencies: {
  dep1: "^1.2.2"
}

npm run

npm不仅可以用于模块管理,还可以用于执行脚本。package.json文件有一个scripts字段,可以用于指定脚本命令,供npm直接调用

{
  "name": "myproject",
  "devDependencies": {
    "jshint": "latest",
    "browserify": "latest",
    "mocha": "latest"
  },
  "scripts": {
    "lint": "jshint **.js",
    "test": "mocha test/"
  }
}

上面代码中,scripts字段指定了两项命令lint和test。 命令行输入 npm run lint,就会执行jshint **.js, 输入npm run test,就会执行mocha test/。 npm run是npm run-script的缩写,一般都使用前者,但是后者可以更好地反应这个命令的本质。

npm内置了两个命令简写,npm test等同于执行npm run test,npm start等同于执行npm run start。

YARN

yarn是由Facebook,Google,Exponent,Tilde联合推出的一个新的JS包管理工具,yarn是为了弥补npm的一些缺陷而出现的。

NPM VS Yarn

将可构建的yarn.lock文件push到代码仓库中, 来确保整个团队都有一个可返回构建运行的版本

比如 package.json

"dependencies": {
    "antd": "^3.4.1",
}

代表使用 3.x.x中最新的版本 在yarn.lock固定了你使用的版本

antd@^3.4.1:
  version "3.6.5"
  resolved "https://registry.yarnpkg.com/antd/-/antd-3.6.5.tgz#bed51a7e8c557a9c6331065087929f8ac4800cb2"
  dependencies:
    array-tree-filter "^2.0.0"
    babel-runtime "6.x"
    classnames "~2.2.0"
    create-react-class "^15.6.0"
    css-animation "^1.2.5"
    dom-closest "^0.2.0"
    enquire.js "^2.1.1"
    省略......

将yarn.lock放到代码仓库中, 来确保整个团队 yarn install 时,都使用 3.6.5 的版本。 当yarn upgrade更新包时候,yarn.lock文件才会更新了,安装包才会更新版本。

axios

スクリーンショット 2020-05-12 16 00 00

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。 执行一个GET请求

スクリーンショット 2020-05-12 15 40 48

执行一个POST请求

スクリーンショット 2020-05-12 15 41 41

调用axios , axios返回的是 promise 对象

React

スクリーンショット 2020-05-09 14 41 08

React的功能其实很单一,主要负责渲染的功能,现有的框架,比如angular是一个大而全的框架,用了angular几乎就不需要用其他工具辅助配合,但是react不一样,他只负责ui渲染,想要做好一个项目,往往需要其他库和工具的配合,比如用redux来管理数据react-router管理路由,react已经全面拥抱es6,所以es6也得掌握,webpack就算是不会配置也要会用,要想提高性能,需要按需加载,immutable.js也得用上, 等等

使用组件化开发方式,符合现代Web开发的趋势

React中的核心概念

虚拟DOM

React将DOM抽象为虚拟DOM,虚拟DOM其实就是用一个对象来描述DOM,通过对比前后两个对象的差异,最终只把变化的部分重新渲染,提高渲染的效率

VituralDOM的处理方式

  1. 用 JavaScript 对象结构表示 DOM 树的结构,然后用这个树构建一个真正的 DOM 树,插到文档当中
  2. 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
  3. 把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了

Diff算法

当你使用React的时候,在某个时间点 render() 函数创建了一棵React元素树, 在下一个state或者props更新的时候,render() 函数将创建一棵新的React元素树, React将对比这两棵树的不同之处,计算出如何高效的更新UI(只更新变化的地方)

スクリーンショット 2020-05-13 13 16 48

key 属性

当在子节点的后面添加一个节点,这时候两棵树的转化工作执行的很好

// 旧
<ul>
  <li>first</li>
  <li>second</li>
</ul>
// 新
<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li>
</ul>

执行过程 React会匹配新旧两个<li>first</li>,匹配两个<li>second</li>,然后添加 <li>third</li> 但是如果你在开始位置插入一个元素,那么问题就来了

// 旧
<ul>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

// 新
<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

在没有key属性时执行过程 React将改变每一个子删除重新创建,而非保持 <li>Duke</li><li>Villanova</li> 不变 为了解决以上问题,React提供了一个 key 属性当子节点带有key属性,React会通过key来匹配原始树和后来的树

// 旧
<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

// 新
<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

执行过程 现在 React 知道带有key '2014' 的元素是新的,对于 '2015' 和 '2016' 仅仅移动位置即可

注意

React的基本使用

安装

npm i -S react react-dom

React元素

React元素 ≠ DOM元素 React元素表示应该如何创建浏览器DOM的一组指令 使用 React.createElement 创建一个React元素来表示 h1 标题元素

React.createElement("h1", null, "Test");

在渲染的过程中,React将会把这个元素转换成实际的DOM元素

因此来说,一个React元素只是一个JavaScript语法,用来告知React如何构造Dom元素 但 createElement() 方式,代码编写不友好,太复杂,推荐使用JSX

使用数据构造元素

使用React的一个优点是它可以将数据和UI元素有效隔离。 比如,将一个数组数据映射为li元素

React.createElement (
 "ul",
  {className: "xxxx"}
  items.map(key => 
       React.createElement("li", null, key))
)

注: map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值

React 组件

React 组件可以让你把UI分割为独立、可复用的片段,并将每一片段视为相互独立的部分。 组件是由一个个的HTML元素组成的

概念上来讲, 组件就像JS中的函数。它们接受用户输入(props),并且返回一个React对象,用来描述展示在页面中的内容

React创建组件的方式

主要使用 ES6 的方式 React.Component 是一个主要特性,兼容ES6规范,它是一个抽象类,用户可以通过它来构建新的组件

class EditableList extends React.Component {
......
    render() {
        return .....
    }
}

无状态组件是 通过 JavaScript函数创建的

无状态函数式组件,顾名思义,无状态,也就是你无法使用State,也无法使用组件的生命周期方法,这就决定了函数组件都是展示性组件,接收Props,渲染DOM,而不关注其他逻辑。

function HelloComponent(props, /* context */) {
  return <div>Hello {props.name}</div>
}
ReactDOM.render(<HelloComponent name="Sebastian" />, mountNode) 

无状态组件的创建形式使代码的可读性更好,并且减少了大量冗余的代码,精简至只有一个render方法,大大的增强了编写一个组件的便利,除此之外无状态组件还有以下几个显著的特点

其实无状态函数式组件也是官方比较推荐的一种方式,尽量让底层的组件变成无状态函数式组件,也即组件尽量只用来显示数据,把数据操作的逻辑都放在顶层,然后从顶层把数据传到底层

JSX

JSX - JavaScript XML 允许用户使用类似HTML的语法来定义React元素 推荐使用 JSX 的方式创建组件 JSX语法,最终会被编译为 createElement() 方法

JSX的语法需要通过 babel-preset-react 编译后,才能被解析执行

function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}

注意

  1. 如果在 JSX 中给元素添加类, 需要使用 className 代替 class 类似,label 的 for属性,使用htmlFor代替
  2. 在 JSX 中可以直接使用 JS代码,直接在 JSX 中通过 {} 中间写 JS代码即可
  3. 在 JSX 中只能使用表达式,但是不能出现 语句 比如不用使用 if else, 只能使用三元表达式
  4. 直接在标签上使用style属性时,要写成style={{}}是两个大括号 属性名同样需要驼峰命名法。即margin-top要写成marginTop

Props

组件中有一个 只读的对象 叫做 props无法给props添加属性 作用是将传递给组件的属性转化为 props 对象中的属性 React把传递给组件的属性转化为一个对象并交给 props

class Welcome extends React.Component {
  constructor(props) {
    super(props)
  }

  render() {
    return <h1>Hello, {this.props.name}</h1>
  }
}

props.children

它表示组件所有的子节点 在组件内部使用 this.props.children,可以拿到用户在组件里面放置的内容

this.props.children 的值有三种可能

所以,处理 this.props.children 的时候要小心。 官方建议使用React.Children.map来遍历子节点,而不用担心数据类型

PropTypes 类型检查

import PropTypes from 'prop-types'

HeaderOne.propTypes = {
   value: PropTypes.string,
   readOnly: PropTypes.bool,
   onChange: PropTypes.func,
   customStyle: PropTypes.object,
   themeColor: PropTypes.string,
}

DefaultProps

HeaderOne.defaultProps = {
  value: undefined,
  readOnly: undefined,
  onChange: () => {},
  customStyle: undefined,
  themeColor: '',
}

类的静态属性

ES6提供类的静态属性,允许用户在类的声明中封装propTypes和defaultProps属性。

class TodoItem extends React.Component {
    static propTypes = {
        name: React.PropTypes.string
    };
    static defaultProps = {
        name: ''
    };
}

State

State是一种内置的数据管理机制,它可以修改组件内部的状态。 当应用程序状态变化时,UI会被重新渲染来反映这些变化。

状态即数据

注意

class SiteViewList extends Component {
   constructor(props) {
      super(props)
      this.state = {
        isShowViewMore: false,
        isShowHide: false,
        isNeed: false,
        initLoading: true,
        loading: true,
        pages: [],
        tags: [],
     }
}

从外部获取数据后,使用setState来绑定状态,在 componentDidMount 里面做。

重新渲染时机

每当组件状态改变时,React 都会调度渲染。 更改状态意味着当我们调用 setState 函数时,React 触发更新。这不仅意味着将调用组件的 render 函数,而且还意味着将重新渲染其所有后续子组件,无论其 prop 是否已更改

setState会触发子组件的render。但是并不一定会刷新DOM有变化才会刷新DOM。 但是触发没必要的render就会触发虚拟Dom对比运算,也是有消耗的。 所以最好redux等状态管理库,来管理数据更新

react render渲染的几种情况

shouldComponentUpdate

这个功能是 React 生命周期的功能之一,它允许我们通过告诉 React 什么时候更新类组件来优化渲染性能。 它的参数是组件的下一个 props 和下一个状态

shouldComponentUpdate(nextProps, nextState) {
// return true or false
}

如果return false的话,组件将不会渲染

数据流和组件间的通信

React单向数据流

父子组件沟通

在React中,最为常见的组件沟通也就是父子了,一般有两种情况

父组件更新子组件状态 很容易理解,传递给子组件的props更新,子组件的render被触发。重新渲染。

第二种子组件更新父组件状态的情况。 一般情况下,只能由父组件通过props传递数据给子组件,使得子组件得到更新,那么现在,我们想实现 子组件更新父组件就需要父组件通过props传递一个回调函数到子组件中,这个回调函数可以更新父组件子组件就是通过触发这个回调函数,从而使父组件得到更新

父组件
search = () => {
    const { userShowLeftMenu } = this.state
    this.setState({
      userShowLeftMenu: !userShowLeftMenu
    })
}

<PageSider
    {...this.props}
    onSearch={this.search}
/>
-------------------------------------------
子组件 PageSider
<button
  onClick={this.props.onSearch}
>

点击子组件的button,会调用父组件的回调函数search,回调函数search里面,通过setState来更新父组件的状态。

兄弟组件沟通

当两个组件处于同一级时,就称为兄弟组件。

按照React单向数据流方式,我们需要借助父组件进行传递,通过父组件回调函数改变兄弟组件的props。 其实这种实现方式与子组件更新父组件状态的方式是大同小异的。

context

除了props来传递回调函数的方式以外,React来提供的context上下文 来实现。 context 表示上下文,将好像组件里面的全局变量一样,指定 context 允许我们将变量从一个组件传递到另一个组件

Context 设计目的是为了让组件共享那些对于一个组件树而言是“全局”的数据

在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的(单向数据流),但这种做法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。 Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。

在父组件中 定义上下文类型

    static childContextType = {
        users: PropTypes.array,
        userMap: PropTypes.object
    }
    // 在父组件中 给context填充数据
    getChildContext() {
        return { // 返回context对象
            users: this.getUsers(),
            userMap: this.getUserMap()
        }
    }
    getUsers = () => {
        // ... 获取数据
    }
    getUserMap = () => {
        // ... 获取数据
    }

子组件,通过 this.context 访问 context 中的数据

    static contextTypes = {
        users: PropTypes.array
    }
    render() {
        return (
                    {this.context.users.map((user, idx) =>( 
                    ....)); }
                    // 
        )
    }

模块

ES6 模块

import命令用于输入其他模块提供的功能 export命令用于规定模块的对外接口

import obj from './a.js'
export default obj

CommonJS 模块化

require 与 module.exports

let obj = require('./a.js')
module.exports = obj

require 在 ES6 和 CommonJS 中都支持 即使我们使用了 ES6 的模块系统,如果借助 Babel 的转换,ES6 的模块系统最终还是会转换成 CommonJS 的规范。

Node 中默认支持 CommonJS 这个服务器端模块化规范,但是对 ES6 的模块化支持并不是太友好,所以需要通过 babel 这个第三方插件在 Node 中来体验高级的 ES6 特性。 nodejs12 开始支持es6模块加载

组件扩展

组件声明周期

组件生命周期函数的定义:从组件被创建,到组件挂载到页面上运行,再到页面关闭组件被卸载,这三个阶段总是伴随着组件各种各样的事件,那么这些事件,统称为组件的生命周期函数

组件的生命周期包含三个阶段:创建阶段(Mounting)、运行和交互阶段(Updating)、卸载阶段(Unmounting)

创建阶段 (Mounting)

该阶段的函数只执行一次

  1. constructor 作用
    • 获取props
    • 初始化state

通过 constructor() 的参数props获取 设置state和props

class Greeting extends React.Component {
  constructor(props) {
    // 获取 props
    super(props)
    // 初始化 state
    this.state = {
      count: props.initCount
    }
  }
}
  1. componentWillMount 在render()之前被调用,因此在这方法里同步地设置状态将不会触发重渲染 无法获取页面中的DOM对象 可以调用 setState() 方法来初始化状态

  2. render 渲染组件到页面中,无法获取页面中的DOM对象

  3. componentDidMount

    • 组件已经挂载到页面中
    • 可以进行DOM操作,比如:获取到组件内部的DOM对象
    • 可以发送请求获取数据( ajax请求外部数据 )
    • 可以通过 setState() 修改状态的值, 注意:在这里修改状态会重新渲染

运行阶段(Updating)

该阶段的函数执行多次

每当组件的props或者state改变的时候,都会触发运行阶段的函数

  1. componentWillReceiveProps
    • 组件接受到新的props前触发这个方法
    • 修改state不会触发该方法

可以通过对比this.props和nextProps并在该方法中使用this.setState()处理状态改变

componentWillReceiveProps(nextProps) {
  //  diff this.props   nextProps
}
  1. shouldComponentUpdate

    根据这个方法的返回值决定是否重新渲染组件,返回true重新渲染,否则不渲染, 如果返回值为false,那么,后续render()方法不会被调用

    //   - 第一个参数:最新属性对象
    //   - 第二个参数:最新状态对象
    shouldComponentUpdate(nextProps, nextState) {
    return nextState.count % 2 === 0
    }
  2. componentWillUpdate

    组件将要更新

    componentWillUpdate(nextProps, nextState) {
    console.warn('componentWillUpdate', nextProps, nextState)
    }
  3. render 重新渲染组件,与Mounting阶段的render是同一个函数

  4. componentDidUpdate 组件已经被更新

    componentDidUpdate(prevProps, prevState) {
    console.warn('componentDidUpdate', prevProps, prevState)
    }

卸载阶段(Unmounting)

组件卸载期间,函数比较单一,只有一个函数,这个函数也有一个显著的特点:组件一辈子只能执行一次!

  1. componentWillUnmount 在卸载组件的时候,执行清理工作,比如
    • 清除定时器
    • 清除componentDidMount创建的DOM对象

高阶组件 HOC

高阶组件(HOC)是 React 中用于重用组件逻辑的高级技术。 HOC 本身不是 React API 的一部分。 它们是从 React 构思本质中浮现出来的一种模式。 具体来说,高阶组件是一个函数,能够接受一个组件并返回一个新的组件。 构建一个简单的hoc

function hello (){
    console.log("hello i  love react ")
}

function hoc(fn){
    return ()=>{
        console.log("first");
        fn();
        console.log("end");
    }
}

hello = hoc(hello);
hello();

React中,一个高阶组件只是一个包装了另外一个 React 组件的 React 组件

实现高阶组件的方法有如下两种

属性代理

属性代理是我们react中常见高阶组件的实现方法,我们通过一个例子来说明

import React,{Component} from 'react';
const MyContainer = (WraooedComponent) => 
    class extends Component {
        render(){
            return <WrappedComponent {...this.props} />
       }
   }

我们想要使用MyContainer这个高阶组件就变得非常容易

import React,{Component} from 'react';
class MyComponent extends Component{
    //...
}
export default MyContainer(MyComponent);

MyContainer这个高阶组件可以做什么?

你可以读取,添加,修改,删除将要传递给 WrappedComponent 的 props 渲染劫持 它被叫做渲染劫持是因为高阶组件控制了 WrappedComponent 生成的渲染结果,并且可以做各种操作。

你可以从外部取得数据,然后将得到的数据(state)作为组件的props传递

function MyContainer(WrappedComponent) {
  return class MyContainer extends React.Component {
    render() {
      const newProps = {
       //  这里可以取得外部数据
        user: currentLoggedInUser
      }
      return <WrappedComponent {...this.props} {...newProps}/>
    }
  }
}

反向继承

const MyContainer = (WrappedComponent)=>{
    class extends WrappedComponent {
        render(){
            return super.render();
        }
    }
}

总结

所有高阶组件都是函数 WrappedComponent是我们希望包装的组件,返回的类MyContainer,主要用于存储和管理state。 比如,在高阶组件MyContainer,取得数据,作为自己的state,当State发生变化并且数据载入完毕,WrappedComponent组件会被渲染,并且state数据,会作为属性props传递给他。

通常的用法是,WrappedComponent组件,负责得到数据,是一个接受props的无状态组件,高阶组件MyContainer负责取得数据,然后传递给WrappedComponent组件,渲染UI

如果不使用高阶组件,WrappedComponent组件内部,需要取得数据,然后渲染UI。 使用了高阶组件,用户就可以构建更多的无状态函数组件,以便可以一心一意的管理UI。 而高阶组件MyContainer负责取得数据。

受控组件 非受控组件

受控组件

在HTML当中,像input,textarea和select这类表单元素会维持自身状态,并根据用户输入进行更新。 在React中,可变的状态通常保存在组件的state中,并且只能用 setState() 方法进行更新. React根据初始状态渲染表单组件,接受用户后续输入,改变表单组件内部的状态。 因此,将那些值由React控制的表单元素称为:受控组件

受控组件的特点

// 当文本框内容改变的时候,触发这个事件,重新给state赋值 handleTextChange = event => { console.log(event.target.value) this.setState({ msg: event.target.value }) }


### 非受控组件
表单数据由DOM本身处理。即不受setState()的控制,与传统的HTML表单输入相似,使用 ref从DOM获取表单值。
表现形式上,react中没有添加value属性的表单组件元素就是非受控组件。

// 通过ref的方式来获取,即获取该节点(非受控组件)

// 直接取相关节点的值 this.refs.username.value


# Redux
<img width="120" alt="スクリーンショット 2020-05-15 22 35 55" src="https://user-images.githubusercontent.com/6970715/82062281-765db880-96fc-11ea-8e35-bd135148cc8d.png">

Redux已经毋庸置疑地成为了众多Flux或类Flux库中的翘楚之一
#### Redux 状态管理工具,用来管理应用中的数据
将组建的State存放在一处,进行统一管理,从而简化了我们在应用程序中查看State的方式。

## 为什么使用 Redux
<img width="400" alt="スクリーンショット 2020-05-15 22 07 14" src="https://user-images.githubusercontent.com/6970715/82059326-7360c900-96f8-11ea-81b5-45a77895a65f.png">
当没有使用Redux时, 兄弟组件间传值将很麻烦,代码很复杂冗余。使用Redux定义全局单一的数据Store,可以自定义Store里面存放哪些数据,整个数据结构也是自己清楚的

#### 从组件角度看,如果你的应用有以下场景,可以考虑使用 Redux
- 某个组件的状态,需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态

## 设计思想
Redux 的设计思想很简单,就两句话
- 所有的状态,保存在一个对象里面
- Web 应用是一个状态机,视图与状态是一一对应的。

## 基本概念

### Store
**Store 就是保存数据的地方**,你可以把它看成一个容器。**整个应用只能有一个 Store**。
Redux 提供**createStore**这个函数,用来生成 Store。

import { createStore } from 'redux'; const store = createStore(fn);

reateStore函数接受另一个函数作为参数,**返回新生成的 Store 对象**

### State
Store对象包含所有数据就是State。
如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。
当前时刻的 State,可以通过store.getState()拿到。

import { createStore } from 'redux'; const store = createStore(fn); const state = store.getState();

Redux中的state是不能直接修改的,只能通过action来修改,相当于我们在单例中定义setter方法。

### Action
- State 的变化,会导致 View 的变化。
- 但是,用户接触不到 State,只能接触到 View。
- 所以,State 的变化必须是 View 导致的。

#### Action 就是 View 发出的通知,表示 State 应该要发生变化了

Action 是一个对象。**其中的type属性是必须的**,表示 Action 的名称。其他属性可以自由设置。

const action = { type: 'ADD_TODO', payload: 'Learn Redux' };

上面代码中,Action 的名称是ADD_TODO,它携带的信息是字符串Learn Redux。
可以这样理解,Action 描述当前发生的事情。**改变 State 的唯一办法,就是使用 Action。它会运送数据到 Store**。

### Action Creator
View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦。可以定义一个函数来生成 Action,这个函数就叫 Action Creator。

const ADD_TODO = 'ADD_TODO'; function addTodo(text) { return { type: ADD_TODO, text } }

const action = addTodo('Learn Redux');

上面代码中,addTodo函数就是一个 Action Creator。

### store.dispatch()
store.dispatch()是 **View 发出 Action 的唯一方法**。

import { createStore } from 'redux'; const store = createStore(fn);

store.dispatch({ type: 'ADD_TODO', payload: 'Learn Redux' });

上面代码中,store.dispatch接受一个 Action 对象作为参数,将它发送出去。
结合 Action Creator,这段代码可以改写如下。

store.dispatch(addTodo('Learn Redux'));


### Reducer
Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。**这种 State 的计算过程就叫做 Reducer**。
Reducer 是一个函数,**它接受 Action 和当前 State 作为参数,返回一个新的 State**

const reducer = function (state, action) { // 计算新的state return new_state; };

整个应用的初始状态,可以作为 State 的默认值。下面是一个实际的例子。

const defaultState = 0; const reducer = (state = defaultState, action) => { switch (action.type) { case 'ADD': return state + action.payload; default: return state; } };

const state = reducer(1, { type: 'ADD', payload: 2 });

上面代码中,reducer函数收到名为ADD的 Action 以后,就返回一个新的 State,作为加法的计算结果。其他运算的逻辑(比如减法),也可以根据 Action 的不同来实现。

实际应用中,**Reducer 函数不用像上面这样手动调用,store.dispatch方法会触发 Reducer 的自动执行**。为此,**Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入createStore方法**。

import { createStore } from 'redux'; const store = createStore(reducer);

上面代码中,createStore接受 Reducer 作为参数,生成一个新的 Store。
**以后每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State**。

Reducer 函数最重要的特征是,**它是一个纯函数**。不得改写参数
由于 Reducer 是纯函数,就可以保证同样的State,必定得到同样的 View。
#### Reducer 函数里面不能改变 State,必须返回一个全新的对象

### store.subscribe()
Store 允许使用store.subscribe方法**设置监听函数**,**一旦 State 发生变化,就自动执行这个函数**

import { createStore } from 'redux'; const store = createStore(reducer); store.subscribe(listener);

显然,只要把 View 的更新函数(对于 React 项目,就是组件的render方法或setState方法)放入listen,就会实现 View 的自动渲染。
store.subscribe方法返回一个函数,调用这个函数就可以解除监听。

let unsubscribe = store.subscribe(() => console.log(store.getState()) ); unsubscribe();


## Store 的实现
上一节介绍了 Redux 涉及的基本概念,可以发现 Store 提供了三个方法。

store.getState() store.dispatch() store.subscribe()

使用它们

import { createStore } from 'redux'; let { subscribe, dispatch, getState } = createStore(reducer);

createStore方法还可以接受第二个参数,**表示 State 的最初状态**。这通常是服务器给出的。

let store = createStore(todoApp, window.STATE_FROM_SERVER)

上面代码中,window.STATE_FROM_SERVER就是整个应用的状态初始值。注意,**如果提供了这个参数,它会覆盖 Reducer 函数的默认初始值**。

## Reducer 的拆分
Reducer 函数负责生成 State。由于整个应用只有一个 State 对象,包含所有数据,对于大型应用来说,这个 State 必然十分庞大,导致 Reducer 函数也十分庞大
Redux 提供了一个**combineReducers**方法,**用于 Reducer 的拆分**。你只要定义各个子 Reducer 函数,然后用这个方法,将它们合成一个大的 Reducer

import { combineReducers } from 'redux'; const chatReducer = combineReducers({ chatLog, statusMessage, userName })

这种写法有一个前提,就是 State 的属性名必须与子 Reducer 同名。如果不同名,就要采用下面的写法。

const reducer = combineReducers({ a: doSomethingWithA, b: processB, c: c })

你可以把所有子 Reducer 放在一个文件里面,然后统一引入。

import { combineReducers } from 'redux' import * as reducers from './reducers'

const reducer = combineReducers(reducers)


## 工作流程
<img width="650" alt="スクリーンショット 2020-05-16 14 28 47" src="https://user-images.githubusercontent.com/6970715/82112664-98e5e500-9781-11ea-86cd-c12767e80d18.png">

1. 首先,用户发出 Action

store.dispatch(action);

2. Store 自动调用 Reducer,并且传入两个参数:**当前 State** 和**收到的 Action**。 Reducer 会返回新的 State

3. State 一旦有变化,Store 就会调用监听函数。

// 设置监听函数 store.subscribe(listener);


4. listener可以通过store.getState()得到当前状态。
如果使用的是 React,这时可以触发重新渲染 View。

function listerner() { let newState = store.getState(); component.setState(newState);
}

一个简单的计数器的例子

// 定义了一个无状态的组件,没有state,只接受props const Counter = ({ value, onIncrement, onDecrement }) => (

{value}

);

// 定义了reducer,处理state的更新 const reducer = (state = 0, action) => { switch (action.type) { case 'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } };

// 创建store const store = createStore(reducer);

// 渲染UI const render = () => { ReactDOM.render( <Counter value={store.getState()} onIncrement={() => store.dispatch({type: 'INCREMENT'})} // 点击,发出Action onDecrement={() => store.dispatch({type: 'DECREMENT'})} // 点击,发出Action />, document.getElementById('root') ); };

render(); // 设置监听函数render, 当state发生变化的时候,会调用render,重新渲染UI store.subscribe(render);


## 中间件与异步操作

异步操作怎么办?Action 发出以后,Reducer 立即算出 State,这叫做同步;Action 发出以后,过一段时间再执行 Reducer,这就是异步。

怎么才能 Reducer 在异步操作结束后自动执行呢?这就要用到新的工具:中间件

中间件就是一个函数,**对store.dispatch方法进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能**
类似

let next = store.dispatch; store.dispatch = function dispatchAndLog(action) { console.log('dispatching', action); next(action); console.log('next state', store.getState()); }


### 中间件使用
常用的中间件都有现成的,只要引用别人写好的模块即可。

const store = createStore( reducer, initial_state, applyMiddleware(thunk, promise, logger) );

**applyMiddleware**方法的三个参数,就是三个中间件。有的中间件有次序要求,使用前要查一下文档。比如,logger就一定要放在最后,否则输出结果会不正确。

applyMiddleware把所有中间件被放进了一个数组chain,然后嵌套执行,最后执行store.dispatch。
中间件内部可以拿到getState和dispatch这两个方法。

### redux-thunk 中间件

redux-thunk 是一个常用的 redux 异步 action 中间件。通常用来处理axios请求。
**redux-thunk 中间件可以让 action 创建函数先不返回一 个action 对象,而是返回一个函数**

要在应用中使用 thunk 中间件,请务必安装 redux-thunk 软件包

npm install --save redux-thunk

在能够编写 thunk action creator 之前,确保我们的 store 已准备好接收中间件:

// store.js import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import rootReducer from '../reducers/root_reducer';

const store = () => createStore(rootReducer, applyMiddleware(thunk)); export default store;

装载好react-thin后, 在 创建Action的文件 actionCreator.js 中
有两种创建Action的方法
第一种,一般情况,创建 action 的函数返回一个对象

export const initListAction = (list) => { return { type: constants.INIT_LIST, list } }

第二种情况,使用了 redux-thunk, action函数可以返回一个函数

export const getTodoList = () => { return (dispatch) => { axios.get('/api/list.json').then(res => { const { data } = res; const action = initListAction(data); dispatch(action); }) } }

使用react-thin后, Action可以是函数类型,
当store执行dispatch的时候,会自动执行改函数

componentDidMount () { const action = getTodoList(); store.dispatch(action); }

react-thin的函数,第二个参数里有个getState参数,可以获取整个store,使用这种方法只能在action里拿到store的数据

# React × Redux
<img width="300" alt="スクリーンショット 2020-05-16 14 46 48" src="https://user-images.githubusercontent.com/6970715/82112906-2c201a00-9784-11ea-80d9-3ce539722cfd.png">

为了方便使用,Redux 的作者封装了一个 React 专用的库 React-Redux
实际项目中,你应该权衡一下,是直接使用 Redux,还是使用 React-Redux。后者虽然提供了便利,但是需要掌握额外的 API,并且要遵守它的组件拆分规范

#### React-Redux 将所有组件分成两大类
- UI 组件
- 容器组件

#### UI 组件有以下几个特征
- 只负责 UI 的呈现,不带有任何业务逻辑
- 没有状态(即不使用this.state这个变量)
- 所有数据都由参数(this.props)提供
- 不使用任何 Redux 的 API

一个 UI 组件的例子。

const Title = value =>

{value}

;


#### 容器组件的特征恰恰相反

- 负责管理数据和业务逻辑,不负责 UI 的呈现
- 带有内部状态
- 使用 Redux 的 API

总之,只要记住一句话就可以了:**UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑**

React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。
也就是说,**用户负责视觉层,状态管理则是全部交给它**

## Connect
React-Redux **提供connect方法,用于从 UI 组件生成容器组件**。
connect的意思,**就是将这两种组件连起来**。

import { connect } from 'react-redux' const VisibleTodoList = connect()(TodoList);

上面代码中,TodoList是 UI 组件,VisibleTodoList就是由 React-Redux 通过connect方法自动生成的容器组件。
但是,因为没有定义业务逻辑,上面这个容器组件毫无意义,只是 UI 组件的一个单纯的包装层。

为了定义业务逻辑,需要给出下面两方面的信息
- 输入逻辑
外部的数据(state)如何转换为 UI 组件的参数(props)
- 输出逻辑
用户发出的动作如何变为 Action 对象,从 UI 组件传出去

所以,完成的connect函数是这样的

import { connect } from 'react-redux' const VisibleTodoList = connect( mapStateToProps, mapDispatchToProps )(TodoList)

上面代码中,connect方法接受两个参数:**mapStateToProps**和**mapDispatchToProps**。
它们定义了 UI 组件的业务逻辑。**前者负责输入逻辑**,即将state映射到 UI 组件的参数(props),**后者负责输出逻辑**,即将用户对 UI 组件的操作映射成 Action。

## mapStateToProps()
mapStateToProps是一个函数。它的作用就是像它的名字那样,建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系。
作为函数,mapStateToProps执行后应该返回一个对象,里面的每一个键值对就是一个映射。
请看下面的例子。

const mapStateToProps = (state) => { return { todos: getVisibleTodos(state.todos, state.visibilityFilter) } }

上面代码中,mapStateToProps是一个函数,它接受state作为参数,返回一个对象。
**这个对象有一个todos属性,代表 UI 组件的同名参数**,后面的getVisibleTodos也是一个函数,可以从state算出 todos 的值。
下面就是getVisibleTodos的一个例子,用来算出todos。

const getVisibleTodos = (todos, filter) => { switch (filter) { case 'SHOW_ALL': return todos case 'SHOW_COMPLETED': return todos.filter(t => t.completed) default: throw new Error('Unknown filter: ' + filter) } }

mapStateToProps会订阅 Store,每当state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。
connect方法可以省略mapStateToProps参数,那样的话,UI 组件就不会订阅Store,就是说 Store 的更新不会引起 UI 组件的更新。

mapStateToProps的**第一个参数总是state对象**,还可以使用**第二个参数ownProps**,代表**容器组件的props对象**。
如果写了第二个参数ownProps,那么当容器组件的prop发生变化的时候,mapStateToProps也会被调用。例如,当 props接收到来自父组件一个小小的改动,那么你所使用的 ownProps 参数,mapStateToProps 都会被重新计算,重新更新UI组件

## mapDispatchToProps()

mapDispatchToProps是connect函数的第二个参数,**用来建立 UI 组件的参数到store.dispatch方法的映射**。
也就是说,**它定义了哪些用户的操作应该当作 Action,传给 Store**。
它可以是一个函数,也可以是一个对象。

如果mapDispatchToProps是一个函数,会得到dispatch和ownProps(容器组件的props对象)两个参数。

const mapDispatchToProps = ( dispatch, ownProps ) => { return { onClick: () => { dispatch({ type: 'SET_VISIBILITY_FILTER', filter: ownProps.filter }); } }; }

从上面代码可以看到,mapDispatchToProps作为函数,
应该返回一个对象,**该对象的每个键值对都是一个映射,定义了 UI 组件的参数怎样发出 Action**。

如果mapDispatchToProps是一个对象,它的每个键名也是对应 UI 组件的同名参数,键值应该是一个函数,会被当作 Action creator ,返回的 Action 会由 Redux 自动发出。举例来说,上面的mapDispatchToProps写成对象就是下面这样。

const mapDispatchToProps = { onClick: (filter) => { type: 'SET_VISIBILITY_FILTER', filter: filter }; }


## Provider 组件

connect方法生成容器组件以后,**需要让容器组件拿到state对象,才能生成 UI 组件的参数**。
一种解决方法是将state对象作为参数,传入容器组件。但是,这样做比较麻烦,尤其是容器组件可能在很深的层级,一级级将state传下去就很麻烦。

React-Redux **提供Provider组件,可以让容器组件拿到state**。

import { Provider } from 'react-redux' import { createStore } from 'redux' import todoApp from './reducers' import App from './components/App'

let store = createStore(todoApp);

render(

, document.getElementById('root') ) ``` Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state了。 **store放在了上下文对象context**上面。然后,**子组件就可以从context拿到store** ``` const { store } = this.context; const state = store.getState(); ``` ## 使用 react-redux + redux-thin 的实例 https://github.com/caochangkui/todolist-react-redux-thunk/tree/master/src # React Hook スクリーンショット 2020-05-18 7 29 09 **v16.8 版本**引入了全新的 API,叫做 React Hooks,颠覆了以前的用法 React Hooks 的意思是,**组件尽量写成纯函数**,**如果需要外部功能和副作用**,**就用钩子把外部代码"钩"进来**。 React Hooks 就是那些钩子。 你需要什么功能,就使用什么钩子。React 默认提供了一些常用钩子,你也可以封装自己的钩子。 **所有的钩子都是为函数引入外部功能,所以 React 约定,钩子一律使用use前缀命名,便于识别**。 你要使用 xxx 功能,钩子就命名为 usexxx。 下面是 React 默认提供的四个最常用的钩子 - useState() - useContext() - useReducer() - useEffect() ### useState useState()用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。 useState的参数为state的初始状态,输出为当前state以及更新state的函数 ``` function Example() { const [count, setCount] = useState(0); return (

You clicked {count} times

); } ``` ### useContext 如果需要在组件之间共享状态,可以使用useContext() 比如,现在有两个组件 Navbar 和 Messages,我们希望它们之间共享状态。 ``` // 第一步就是使用 React Context API,在组件外部建立一个 Context。 const AppContext = React.createContext({});
``` 在Navbar 组件,使用useContext 来引入context的内容。 ``` const Navbar = () => { const { username } = useContext(AppContext); return (

AwesomeSite

{username}

); } ``` ### useReducer useReducer()的基本用法,它接受 Reducer 函数和状态的初始值作为参数,返回一个数组。 数组的第一个成员是状态的当前值,第二个成员是发送 action 的dispatch函数。 例如,用于计算状态的 Reducer 函数如下 ``` const myReducer = (state, action) => { switch(action.type) { case('countUp'): return { ...state, count: state.count + 1 } default: return state; } } ``` 组件代码如下 ``` function App() { const [state, dispatch] = useReducer(myReducer, { count: 0 }); return (

Count: {state.count}

); } ``` ### useEffect useEffect()用来引入具有副作用的操作, #### 最常见的就是向服务器请求数据。 以前,放在componentDidMount里面的代码,现在可以放在useEffect()。 useEffect()的用法如下 ``` useEffect(() => { // Async Action }, [dependencies]) ``` 上面用法中,useEffect()接受两个参数。 第一个参数是一个函数,异步操作的代码放在里面。 第二个参数是一个数组,用于给出 Effect 的依赖项,只要这个数组发生变化,useEffect()就会执行。 第二个参数可以省略,这时每次组件渲染时,就会执行useEffect()。 下面看一个例子,这个一个无状态的函数组件 - Person组件 ``` const Person = ({ personId }) => { const [loading, setLoading] = useState(true); const [person, setPerson] = useState({}); useEffect(() => { setLoading(true); fetch(`https://swapi.co/api/people/${personId}/`) .then(response => response.json()) .then(data => { setPerson(data); setLoading(false); }); }, [personId]) if (loading === true) { return

Loading ...

} return

You're viewing: {person.name}

Height: {person.height}

Mass: {person.mass}

} ``` 上面代码中,每当组件参数personId发生变化,useEffect()就会执行。组件第一次渲染时,useEffect()也会执行。 # React Router スクリーンショット 2020-05-18 9 06 49 路由库React-Router。它是官方维护的,事实上也是唯一可选的路由库。它通过管理 URL,实现组件的切换和状态的变化。 它也分为 - react-router 核心组件 - react-router-dom 应用于浏览器端的路由库(单独使用包含了react-router的核心部分) - react-router-native 应用于native端的路由 进行网站(将会运行在浏览器环境中)构建,我们应当安装react-router-dom。 react-router-dom暴露出react-router中暴露的对象与方法 因此你只需要安装并引用react-router-dom即可 ``` yarn add react-router-dom # 或者 npm install react-router-dom ``` #### React Router中的组件主要有三类 - routers, 路由,例如`` 、 `` - route matchers,路由匹配 如 `` 、`` - navigation,导航,如 ``、 ``、`` ## BrowserRouter组件 BrowserRouter主要使用在浏览器中,也就是WEB应用中。**它利用HTML5 的history API来同步URL和UI的变化**。当我们点击了程序中的一个链接之后, **BrowserRouter就会找出与这个URL匹配的Route,并将他们对应的组件渲染出来**。 BrowserRouter是用来管理我们的组件的,那么它当然要**被放在最顶级的位**置,而**我们的应用程序的组件就作为它的一个子组件而存在** ``` ``` #### basename 属性 当前位置的基准 URL ``` // 被渲染为 ``` ## Link组件 Link就像是一个个的路牌,为我们指明组件的位置。 **Link使用声明式的方式为应用程序提供导航功能**,定义的Link最终会被渲染成一个a标签。 **Link使用to这个属性来指明目标组件的路径**,可以直接使用一个字符串,也可以传入一个对象。 ``` import { Link } from 'react-router-dom' // 字符串参数 查询 // 对象参数 查询 ``` #### to=对象的情况 带参数跳转(pathname, search, hash, state(额外数据)) 这些参数都被存放到**this.props.location**中 #### 属性:replace: bool 当设置为 true 时,点击链接后将使用新地址替换掉访问历史记录里面的原地址。 当设置为 false 时,点击链接后将在原有访问历史记录的基础上添加一个新的纪录。 默认为 false。 ``` ``` ## NavLink组件 **NavLink是一个特殊版本的Link** - 可以使用activeClassName来设置Link被选中时被附加的class - 使用activeStyle来配置被选中时应用的样式 此外,还有一个exact属性,此属性要求location完全匹配才会附加class和style。 这里说的匹配是指地址栏中的URl和这个Link的to指定的location相匹配。 ``` // 选中后被添加class selected Home // 选中后被附加样式 color:red Gallery ``` ## Route组件 Route应该是react-route中最重要的组件了,它的作用是当location与Route的path匹配时渲染Route中的Component。**如果有多个Route匹配,那么这些Route的Component都会被渲染**。 ``` ``` Route有一个**exact属性**,作用也是**要求location与Route的path绝对匹配**。 ``` ``` #### Route的三种渲染方式 - component 这是最常用也最容易理解的方式,给什么就渲染什么 - render render的类型是function,Route会渲染这个function的返回值。 因此它的作用就是附加一些额外的逻辑。 ``` { console.log('额外的逻辑'); return (
Home
); }/> ``` - children 它同render类似,是一个function。不同的地方在于它会被传入一个**match参数**来告诉你这个Route的path和location匹配上没有. 即使path没有匹配上,我们也可以将它渲染出来。秘诀就在于前面一点提到的match参数。我们可以根据这个参数来决定在匹配的时候渲染什么,不匹配的时候又渲染什么。 ``` // 在匹配时,容器的calss是light,会被渲染 // 在不匹配时,容器的calss是dark,会被渲染 (
{match ? :}
)}/> ``` ### 所有路由中指定的组件将被传入以下三个 props ``` const { match, location, history } = this.props; ``` - **match** ``` const { page } = this.props.match.params.page ``` - **Location** 一个Location的大概形式是这样的 ``` { key: 'ac3df4', // 在使用 hashHistory 时,没有 key pathname: '/somewhere' search: '?some=search-string', hash: '#howdy', state: { [userDefined]: true } } ``` 比如访问 `http://localhost:1234/cms/publish/sites/12?key1=value1&key2=value2` 常用的是 , query String 通过search来获得参数 ``` import queryString from 'query-string' const values = queryString.parse(this.props.location.search) console.log(values); { key1: value1 key2: value2 } ``` 还可以取得当前的path ``` this.props.location.pathname publish/sites/12 // 相对路径,相对于外面 BrowerRouter ``` - **history** history对象中最重要的属性就是 location 。**location对象表名了你的应用当前位于什么位置**。 它包含了一些从URL中获取到的属性:pathname , search, hash。 除此之外,每个location对象中都一个与之相关的唯一的'key'属性。这个'key'可以用于鉴别跟存储特定location的相关数据。 最后,每个location对象都有一个与之相关的state,这提供了一种在location上添加一些信息,但是这些信息不会展示在URL上的方法。 #### 尽管我们可以访问的当前路径只有一个,但是history对象保持着对一些路径的跟踪。 正是这种能够在location数组中添加,移除位置的能力让history对象具有了记录历史的功能 如果history对象只知道当前路径的相关信息,那它就更应当叫做 present(当前)。 除了在对象中保存着一个路径数组,**history对象也维护着一个索引值,这个索引表示着当前所在路径在这个路径数组中的位置**。 #### Push **Push方法可以让你导航到一个新的地址** 这个操作会在当前的location数组之后添加一个新的地址。 **默认情况下,当你点击通过React router生成的的时候,调用的是history.push方法** ``` history.push('/templates/new') ``` #### Replace Replace方法跟push方法类似,但是不同的是Replace是在当前索引的位置替换地址而不是在当前索引的位置之后添加一个新的地址。位于当前索引位置之后的地址不会被清楚。 **对于重定向来说使用replace这个方法是很好的**,这也正是React router中的组件所采取的方法。 #### go(n) 将 history 对战中的指针向前移动 n #### goBack() 等同于 go(-1) #### goForward() 等同于 go(1) ## Redirect组件 当这个组件被渲染是,location会被重写为Redirect的to指定的新location。 ``` ``` ## Switch组件 渲染匹配地址 location 的第一个 ``或者`` 这与只使用一堆``有什么不同? ``的独特之处是独它仅仅渲染一个路由。 相反地,没有 `` 每一个包含匹配地址 location 的``都会被渲染。 ``` import { Switch, Route } from 'react-router' ``` 现在,如果我们处于 /about, `` 将开始寻找匹配的 ``。 `` 将被匹配, `` 将停止寻找匹配并渲染``。 同样,如果我们处于 /michael ,` ` 将被渲染。 # WebPack スクリーンショット 2020-05-19 9 43 21 本质上,webpack 是一个现代 JavaScript 应用程序的**静态模块打包器**。 当 webpack 处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle Webpack 需要一个 webpack.config.js 的配置文件,它只是一个 CommonJs 模块 ``` module.exports = { entry: './main.js', output: { filename: 'bundle.js' } } ``` 有了这个文件之后,就可以用webpack进行打包处理了 ``` webpack --config ./webpack.prod.js webpack-dev-server --config ./webpack.dev.js --open ``` #### 四个核心概念 - 入口(entry) - 输出(output) - loader - 插件(plugins) #### 入口 入口起点(entry point)指示 webpack 应该使用哪个模块,**来作为构建其内部依赖图的开始**。 进入入口起点后,**webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的**。 **每个依赖项随即被处理,最后输出到称之为 bundles 的文件中** 可以通过在 webpack 配置中配置 entry 属性,来指定一个入口起点(或多个入口起点) ``` entry: { src: path.join(__dirname, './src/index.js') }, ``` 分离 应用程序(app) 和 第三方库(vendor) 入口 ``` const config = { entry: { app: './src/app.js', vendors: './src/vendors.js' } }; ``` #### 出口 output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件 默认值为 `./dist`。 基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。 你可以通过在配置中指定一个 output 字段,来配置这些处理过程: ``` output: { filename: '[name].[hash].js', path: path.join(__dirname, './dist'), chunkFilename: '[name]_[contenthash:8].js', publicPath: '/cms/', }, ``` #### loader loader 让 webpack 能够去处理那些非 JavaScript 文件。 loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。 本质上,**webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块** ``` module: { rules: [ { test: /.(jsx|js)$/, loader: 'babel-loader', exclude: [path.join(__dirname, './node_modules')], options: { babelrc: true, }, }, { test: /\.html$/, loader: 'html-loader', }, { test: /\.(jpg|jpeg|png|gif|svg)$/, exclude: path.join(__dirname, './node_modules'), loader: 'url-loader', }, ], }, ``` ・test 属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。 ・use 属性,表示进行转换时,应该使用哪个 loader。 #### 插件 loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。 插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。 想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建它的一个实例。 ``` const HtmlWebpackPlugin = require('html-webpack-plugin') const CopyWebpackPlugin = require('copy-webpack-plugin') const ProgressBarPlugin = require('progress-bar-webpack-plugin') plugins: [ new ProgressBarPlugin({ // Cool... complete: Chalk.bgGreen(' '), incomplete: Chalk.bgWhite(' '), format: `:bar ${Chalk.green(':percent')} (:elapsed seconds)`, clear: false, }), new CopyWebpackPlugin([{ from: './src/static/fonts/', to: 'fonts' }]), new HtmlWebpackPlugin({ template: path.join(__dirname, './src/index.html'), }), ], ``` ## 实例 ### Entry file ``` module.exports = { entry: './main.js', output: { filename: 'bundle.js' } } ``` ### Multiple entry files 多个入口文件也是可以的。在多页面 应用中,每个页面拥有不同的入口文件,用这个就非常管用了 ``` module.exports = { entry: { bundle1: './main1.js', bundle2: './main2.js' }, output: { filename: '[name].js' } } ``` ### Babel-loader Loaders 是一种预处理器,它可以在 Webpack 编译之前把你应用中的静态资源进行转换。 举个例子, Babel-loader 可以在 Webpack 编译这些 JS 文件之前,先将 JSX/ES6 语法的文件转换成普通 ES5 语法的文件。Webpack 官网可以查看目前支持的 loaders。 ``` module.exports = { entry: './main.js', output: { filename: 'bundle.js' }, module: { rules: [ { test: /\.jsx?$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['es2015', 'react'] } } } ] } } ``` ### CSS-loader Webpack 允许在 JS 文件里包含 CSS,并通过 CSS-loader来预处理 CSS 文件。 ``` module.exports = { entry: './main.js', output: { filename: 'bundle.js' }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] } ] } } ``` **你必须用到两个 loaders 去转换 CSS 文件**。 一个是 **CSS-loader** 来读取 CSS 文件 另一个是 **Style-loader** 用来把