Giter Club home page Giter Club logo

noob_issues_blog's Introduction

Hi there 👋 ,I’m WeiSheng_

  • 🤔 I’m currently learning React, Vue.js, TypeScript and Rust.
  • 🧑‍🎨 I love drawing and programming.
  • 🔭 I'm a Front-End Engineer.

noob_issues_blog's People

Contributors

weishengv99 avatar

noob_issues_blog's Issues

Node.js的基础介绍

Node在底层通过一个叫做libuv的库去访问操作系统的非阻塞网络调用。

这个库会去管理线程池,借助它来模拟出一种非阻塞现象。它提供快速/跨平台/非阻塞I/O的本地库

在Node中。v8模块只是负责js的编译和执行,它可以把js直接编译成机器码,还能为代码做一些优化,所以node才可以这么快。被用作JS的运行时。

node可以启用3个阶段的特性,如果你想使用新的特性,只需要在启动node的时候带上某些参数,不能版本提供的新特性当然也不一样。(ES5,ES6)

node —inspect —debug-brk 可以让Node启用调试器,并停在第一行,你可以在chrome中进行调试

Node程序一般有3种,web应用程序,命令行工具,和后台程序,桌面程序。

Node.js的基础编程

创建程序的时候,不论是什么工具,基本不可能把所有代码放到一个文件里。所以在代码分割上,传统的做法是按逻辑相关性对代码分组,将包含大量代码的单个文件分解成多个文件。

在某些语言中,比如PHP和Ruby,整合其他文件,可能会影响全局作用域,他们会提供一个叫做命名空间的方式来避免这个问题,但是在Node当中,我们不需要担心这个问题,Node不会给你污染全局作用域的机会。

创建一个基本的Node.js应用

mkdir my_module
cd my_module
npm init -y

-y参数的意思是使用默认选项,然后npm就会创建一个使用默认值的package.json的文件,

Node.js模块的寻找路径

模块可以是一个文件,也可以是包含多个文件的目录,如果是一个目录,node通常会去寻找目录下一个文件名字是index.js的文件(具体寻找路径可以去官网查看详细说明),模块的寻找会与NODE_PATH有关(环境变量),它可以改变Node模块的默认路径,

require的解析

require一个模块还是node当中少数的同步IO操作之一,它会阻塞node进程,如果你在每个http请求的处理过程中去require一个文件,那么就会遇到性能问题。

如果你重写了到处的模块export对象,并且同时又个module.export 对象,那么export会被忽略,

Node.js可以把模块作为一个对象缓存起来,如果程序中2个文件引用了同一个模块,第一个require会把模块返回的数据缓存到内存当中,这样第二次require的时候就不用再去访问和计算模块的源文件了。(也就是说,require2次得到的模块对象是同一个对象)

事件——异步编程

Node.js当中的模块大多是继承自EventEmitter,比如http模块,本质上事件监听器就是一个回调,他和事件相关联,Node.js的http模块就是一个事件发射器。

Web数据去的选择

我们首先考虑SQLite数据库,它是一个进程内数据库,所以比较方便(不太理解):你不需要再系统上安装一个后台运行的数据库,你添加的所有数据都会写到一个文件里。也就是说程序停掉后再起来的时候数据还在,所以比较适合新手入门的时候学习使用。

React生命周期

React生命周期

关于一个前端框架,对框架最基本的了解应该是它的生命周期,下图是我在查阅过程中总结的react的生命周期示意图。

image-20190823112017154

getDerivedStateFormProps(nextProps,prevState)

他可以返回一个state,表示我们需要更新的state,也可以返回一个null,表示不需要更改state。

getSnapshotBeforeUpdate(prevProps,prevState)

他的返回值可以在componentDidMount中作为第三个参数,他的调用时机看上图,Dom还没变。(他可以读取未改变的Dom状态)

setState异步/同步

setState异步/同步

关于this.setState是异步还是同步的问题困扰了很久。

在React中,如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用setState不会同步更新this.state,除此之外的setState调用会同步执行this.state。所谓“除此之外”,指的是绕过React通过addEventListener直接添加的事件处理函数,还有通过setTimeout/setInterval产生的异步调用。

// setState不会立刻改变React组件中state的值;
const currentCount = this.state.count;
this.setState({count: currentCount + 1});
this.setState({count: currentCount + 1});
this.setState({count: currentCount + 1});
console.log(this.state.count);
// 多次setState函数调用产生的结果会合并
this.setState({FirstName: 'Morgan'});
this.setState({LastName: 'Cheng'});
// 连续调用了两次this.setState,但是只会引发一次更新生命周期
// 不是两次,因为React会将多个this.setState产生的修改放在一个队列里
// 缓一缓,攒在一起,觉得差不多了再引发一次更新过程。
// 相当于如下:
this.setState({FirstName: 'Morgan', LastName: 'Cheng'});

setState通过引发一次组件的更新过程来引发重新绘制,他的调用引发了生命周期的4个函数的调用(依次调用)

  1. shouldComponentUpdate. 他调用的时候this.state并没有更新,如果返回false,更新中断,但是this.state还是会更新*
  2. componentWillUpdate 他调用的时候this.state也没有更新
  3. render 这时候this.state才更新
  4. componentDidUpdate

从上面的顺序来看,比props的变更少一个componentWillReceiveProps函数的调用

为了实现合并等功能,他就是要异步调用更新,但是setState可以传递一个函数

// 第一个是当前的state值,第二个是当前的props
// 每次函数运行时接到的state是同一个对象,但他不是真正的state
function increment(state, props) {
  console.log('后运行')
  return {count: state.count + 1};
}
this.setState(increment);
this.setState(increment);
this.setState(increment);
console.log('先运行')
            
            
            
// 2种我都用
this.setState(increment);
this.setState(increment);
this.setState({count: this.state.count + 1});
this.setState(increment);
// 结果是 2 不是 4,因为半路杀出的传统式调用导致函数式的调用效果清空。

虽然函数异步的更新state,但是我们可以写出这种更新方式

对浏览器事件循环的理解🤔

事件循环是什么

学习JavaScript的话肯定听说过 event loop这个概念,我们都知道JavaScript是单线程的,这种单线程模型,是浏览器用户不乐意见到的,对于用户来说,他们不可能会想在浏览器执行网络访问的或文件获取这样的低速操作的时候干等着,这是不合理的,为了解决这个问题,浏览器引入了事件机制。
我们必须理解event loop的规则,才能预防我们写出不能理解的bug,了解代码的运行情况。

Js stack

JavaScript有一个单独的调用堆栈,当你执行某个函数的时候,它会被添加到调用堆栈中(如果你对堆栈不太熟悉,可以google了解一下堆栈这个概念),如果一个函数调用另外一个函数,那么另一个函数将位于调用堆栈中的第一个函数的上面,你可以打开google的source页面,在debuggr模式下观察右边的Call Stack面板,它会记录当前堆栈。

如下图所示

js-stack

我们可以看到call stack的详细信息,当然,你也可以使用vscode 的debug 来查看栈信息。

当你在写了一个bug,导致了js报错,你会在console面板看到没有被catch的error信息,error信息对象里也有调用栈的确切路径。

Task(Macrotasks)

有的人会称它为宏任务,这是一个任务队列,你setTimeout的回调,鼠标的点击事件等等都会放入到这个事件队列当中,他并不会立即执行,他会在event loop开始的时候把头一个函数发送到调用堆栈 Js stack ,每个task任务之间,浏览器可能会进行一次render(毕竟你也许并没有改变视图)。

Microtasks

它被普遍称为微任务,通常在当前调用栈为空的时候才会执行,一般是promise,observer的callback,微任务队列会把队列中的函数一次性全部执行完,微任务执行过程中添加的微任务会放在队列的后方,也会在本次事件循环中被执行。

Animation callbacks

这是一个特殊的队列,由requestAnimationFrame的回调构成,它类似于微任务的执行方式,但是递归的函数调用并不会一直执行下去,而是在下一个事件循环的时候才会执行。

执行顺序

在介绍了每个队列之后,我们来看看具体的执行顺序

event-loop

这是一个比较经典的图(如有侵权,请联系我删除),我们可以看到这就像是一个圈⭕️,

它在 Js stack 执行玩以后 会去检查 task 队列,如果task队列有任务需要执行, task 队列会把头一个任务 发送到 Js stack 执行,当这个任务执行完后, loop会去检查Microtasks,并把Microtasks队列里的任务全部执行完,清空任务队列,然后会在浏览器渲染前 去执行 Animation callbacks队列, 执行完浏览器就会 render ,最后又会开启下一轮 loop,这就是javascript 在浏览器里的大致运行顺序。

浏览器还对loop做了一定的优化,包括心跳检查等等,这些就不具体描述了(其实我也不懂😢)

总结

人们在聊event loop的时候,往往只会聊Macrotasks和Microtasks,却忽略了Animations callback 和js stack的存在,对loop的一知半解就像是管中窥豹,盲人摸象,在现代框架的封装下,往往又接触不到具体细节,所以有时候会遇到莫名其妙的bug而不知道怎么解决,我对event loop也只了解这一部分,希望本篇文章对你有帮助。

关于同步代码中用Dom修改CSS,浏览器会优化到最后一个的情况,详见参考资料,也许会对你有些帮助,目前我对这方面不是特别了解。

🐴 谢谢阅读。

参考资料

https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/?utm_source=html5weekly

https://zhuanlan.zhihu.com/p/45111890

计算机里的数据存储方式

每当你声明一个变量,赋值一个变量的时候,你是否清楚的了解你到底做了什么,这个数据再计算机种是如何存储的,当我们遇到下面这种情况时,你是否会错愕。

0.1 + 0.2  !== 0.3
'😈'.length === 2

1637419295(1)

这都是 js 程序员经常会遇到的问题,所以了解 整数 浮点数 字符串 等在计算机里的存储方式,是一件必要的事情。

基本的存储单位

单位 定义描述
位(bit) 指二进制中的一位,是计算机内部信息的最小单元,例如 111000 是一个 6bit 的二进制数据
字节(byte) 通常用作计算机信息计量单位,不分数据类型。是通信和数据存储的概念。一个字节代表八个比特。从历史的观点上,“字节”表示用于编码单个字符所需要的比特数量。历史上字节长度曾基于硬件为1-48比特不等,最初通常使用6比特或9比特为一字节。今日标准以8比特作为一字节,因8为二进制整数。八个比特在一些规范(例如工业标准、计算机网络、电信技术等)中常被称为八位组。
字(word) 字由若干个字节组成,一个字节是8个比特bit。字的位数叫做字长,即cpu一次处理二进制代码的位数。字的长度与计算架构有关,比如32位机,一个字就是32位,换算成字节就是4字节;同样的64位机,一个字就是64位,也就是8字节。字也是计算机一次处理数据的最大单位。。
字符(character) 在电脑和电信领域中,字符(Character)是一个信息单位。对使用字母系统或音节文字等自然语言,它大约对应为一个音位、类音位的单位或符号。简单来讲就是一个汉字、假名、韩文字……,或是一个英文、其他西方语言的字母。 电脑和通信设备会在表示字符时,会使用字符编码。是指将一个字符对应为某个东西。传统上,是代表整数的比特序列,如此,则可透过网络来传输,同时亦便于存储。两个常用的例子是ASCII和用于Unicode编码的UTF-8。

并查集(union find)

并查集(union find)数据结构学习记录

动态连通性 (Dynamic connectivity)

如下图。我们给了几个数字 数字之间是否相连来判断是否是同一个集合 , 下图就只有2个集合

进阶的使用,可以用来判断 p 和 q点是否相连

Rx.js 笔记

Rx.js 笔记

安装

首先rxjs的不同版本的Api差距很大,v4和v5的基本实现方式不一样,所以我们需要区分。

npm i rxjs        ##v5版本
npm i rx          ##v4版本
import Rx from 'rxjs/Rx';
import Rx from 'rxjs';

//深链
import { Observable } from 'rxjs/Observable';

上面的导入方式是将 Rx 整个库全部导入进来。有的时候,我们一个项目并不需要一整个Rx的全部功能,为了防止打包后体积过大,我们可以采取 深链 的方式来应用Rx。

关于TreeSharking对Rx起不了太大的作用,因为Rx在底层Observale这个类几乎引用了所有的代码

观察者模式

image-20190823113047792

Observable (可以被观察的对象)

可以看成发布者(不是生产数据的生产者,看Hot章)

Observer(观察者)

连接这两个的桥梁就是(subscribe)

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
//使用分开引入的方式添加方法

//source就是可被观察者 console.log扮演了观察者	也可以看到subscribe充当一个桥梁
const source = Observable.of(1, 2, 3);  
source.subscribe(console.log);          

迭代器模式

提到迭代器,在js中的迭代器,我们是调用next函数来一个一个吐出数据的模式。

实现迭代器,也就是(游标),遍历一个数组。

但是无论怎么实现这个设计,一般都有几个通用的Api

  • getCurrent 获取当前 指针指向的元素
  • moveToNext 将 指针移到下一项元素
  • isDone 判断是否已经遍历完一个集合

在Rxjs 中我们不需要去(拉)(调用getCurrent)数据。我们只要通过subScribe连接上Observeable后,就可以收到消息,这就是(推)

Observable简单实例

const onSubscribe = observer => {
  observer.next(1);
  observer.next(2);
  observer.next(3);
};
const source$ = new Observable(onSubscribe);
const theObserver = {
  next: item => console.log(item)
};
source$.subscribe(theObserver);
  1. 创建一个onSubscribe函数,他会作为Observable的参数,决定了Observable的行为,当调用next的时候,将数据传给Observer。
  2. 调用Observable构造函数,生成一个source$数据流(由于rxjs推数据的形式就像一个流)
  3. 创建观察者Observer。
  4. 通过subscribe将theObserver和source$关联起来

在函数底层:

  1. 定义了onSubscribe函数,这个函数作为参数传递给Observable构造函数,创建了一个source$对象。

    这个时候,onSubscribe并没有被调用,他只是在等待source$的subscribe被调用)

  2. theObserver对象被创建。

  3. source的subscribe方法被调用,theObserver成为了source​的观察者。

    每有一个subsrcribe调用,都会重新运行onSubscribe函数。

    就在调用subscribe的时候,onSubscribe函数才会被调用,在运行中函数的参数observer代表的就是观察者theObserver,但并不是theObserver对象本身,在这里,rxjs实际上会对观察者做一个包装,在这里参数observer就是被包装的theObserver,所以二者完全不一样,可以理解为参数observer代理了theObserver,就像vue的data,methods等一样,对参数observer的所有函数调用都会转移到theObserver的同名函数上去

  4. 在onSubscribe函数中,连续调用了三次next方法,通过observer的代理,也就是调用了theObserver的next方法被调用了三次,所以console.log运行了三次,打印出三个数字

Observable异步推送和关闭

const onSubscribe = observer => {
  let number = 1;
  const handle = setInterval(() => {
    observer.next(number++);
    if (number > 6) {
      clearInterval(handle);
    }
  }, 1000);
};

想要异步推送当然只需要异步调用observer的next方法就可以了,理所当然的逻辑。

之所以可以这样做,是因为observer参数完全代理了观察者对象theObserver,对于观察者来说,他不需要操心任何事情,只需要被动的接受数据来处理即可,也不必担心数据什么时候产生。

调用next方法的操作有时被称为emit

如果我们不清除定时器(clearInterval),那么观察者会一直接受到数据,Observable可以推送的数据是∞的,但是这个程序并不会消耗更多的内存(不会再⬆️)这是因为Observable对象每次只吐出一个数据,然后这个数据就被Observable消化处理了,不会存在数据的堆积。

⚠️这种数据的处理方式,和吧数据都堆积在一个数组里,然后挨个处理数据有很大的差别。

如果我们关闭了定时器,不再继续推送数据,那么这个数据流理应结束,因为观察者不会在收到数据,当实际上并没有,我们需要手动给予Observer一个信号,告诉他Observable对象已经完结的信号。

//我们在theObserver对象上添加了一个complete的方法,代表没有更多的数据会被传入。
const theObserver = {
  next:item => console.log(item),
  complete:() => console.log('the end')
}
//我们需要用参数observer代理的方式来调用complete方法
const onSubscribe = observer => {
  let number = 1;
  const handle = setInterval(() => {
    observer.next(number++);
    if (number > 6) {
      observer.complete();
      clearInterval(handle);
    }
  }, 1000);
};

Observable的出错处理

//除了告诉推送给观察者  数据/结束信号之外,我们还要给观察者一个出错信号。
const onSubscribe = observer => {
  observer.next(1);
  observer.error('404 not found')
  observer.complete();
};

const theObserver = {
  next: item => console.log(item),
  complete: () => console.log('the end'),
  error: err => console.log(err)
}

如果运行代码就会发现。complete函数没有运行。

在Rx中,一个Observable对象只有一个终结状态,要么complete,要么error!

image-20190823114749303

⚠️之前提到observer只是代理theObserver,所以调用complete函数并没啥卵用。

Observer简化

对于theObserver对象来说,如果complete的时候不需要做一些特殊的操作的话,那么可以不提供complete方法,比如不会结束的Observable,complete函数毫无用处。

如果没有提供error方法却出错了,那么这个错误会一直往外抛,所以error方法有比较👌。

对于subscribe函数来说,也可以接受的不是一个对象,可以直接接受3个函数

source$.subscribe(theObserver)

source$.subscribe(
  item => console.log(item),
  err => console.log(err),  //null
  () => console.log('the end')
)

如果不关心某个处理函数,比如err函数,可以直接把函数替换成null

退订Observabe

有观察,有退订是理所当然的事情。

我们通过subscribe建立Observer和Observable的联系,要通过unsubscribe退订。

const onSubscibe = observer => {
  let count = 1
  const time = setInterval(() => {
    observer.next(count);
    count++
  },1000);
  return {
    unsubscribe: () => {
      clearInterval(time)
    }
  }
}

可以看到onSubscibe函数返回了一个对象,其中包含了一个unsubscribe方法,只要调用这个方法,就可以清除定时器,来退订。

Hot/Cold Observable

如果一个Observable被二个Observer订阅。但是不是同时订阅,一号比二号早5秒,那么对于二号来说是不是那5秒的数据丢失了呢。

对于不同的需求场景,我们希望有不同的结果。

  1. 没丢失,比如支付宝的通知,你不能丢失吧。(Cold)

  2. 丢失了,比如直播,错过也就丢失了吧。(Hot)

对于Cold来说,没丢失就相当于每次订阅都会产生一个对应的推送中心(每次订阅都会产生闭包的感觉)

const cold$ = new Observable( observer => {
  const producer = new Producer();
  //然后然observer去接受producer产生的数据
})

对于Hot来说,概念上有个独立的生产者,他的创建和subscribe的调用没有关系,sub的调用只是然Observer连接上生产者。

const producer = new Producer();
const Hot$ = new Observable( observer => {
  //然后然observer去接受producer产生的数据
})

所以想要HOT还是COLD都不是通过控制Observable来实现的,实现的方法只有通过生产者自身来控制,他是HOT还是COLD就决定Observable是HOT还是COLD

操作符

image-20190823115212919

在实际运用Rx的时候,往往需要对数据进行一些处理,也就是操作。

对于每个操作符来说,连接的就是上游和下游,可以想象Node的pipe函数,只不过有点不同。

简单的实例:

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
const onSunsrcibe = observer => {
  observer.next(1);
  observer.next(2);
  observer.next(3);
};
const source$ = Observable.create(onSunsrcibe);
source$.map(x => x ** 2).subscribe(console.log);

可以看到创建发布者的创建不自由通过New一个的方式来创建。

第一个操作符是Observable自带的create,意思是创建一个Observable对象。

第二个就是map,对于js来说这玩意太熟悉了,就是差不多的意思。

⚠️(只不过生产的不是数组了, 而是一个新的Observable对象 )

总而言之,我们可以把操作符看成--reducer

如果要给一个定义:

一个操作符是返回一个Observable对象的函数,不过,有的操作符是根据其他Observable对象产生返回的Observable对象,有的是利用其他类型的输入产生返回的Observable对象,还有一些操作符不需要输入就可以凭空创造一个Observable对象。

操作符和弹珠图

对于rxjs数据流的形式简单来说就是单项的数据流,稍微复杂的就需要使用弹珠图来辅助。

image-20190823115330739

上图就是一个简单的弹珠图,意思就是每间隔一段时间后吐出一个递增的正整数。

根据弹珠图的格式,| 竖杠代表的是数据流的结束。也就是complete函数的调用。x 叉号就是数据流的异常,也就是error函数的调用。所以这⚠️二个符号不可能同时存在

image-20190823115349569

为了表达操作符,就会用上下游的方式来表达,如下:

image-20190823115404539

根据功能可分为:

  1. 创建类(例如create)
  2. 转化类 (例如map)
  3. 过滤类(例如filter)
  4. 合并类
  5. 多播类
  6. 错误转化类
  7. 辅助工具类
  8. 条件分支类
  9. 数学和合计类
  10. 背压控制类
  11. 可连接类
  12. 高阶Observable处理类

从存在形式来分。( 静态) 和( 实例)分类

🌰 Observable.of是静态方法,Observable.prototype.map是实例方法

在导入的时候也是有区别的:

import  'rxjs/add/observable/of'
import  'rxjs/add/operator/map'

当然,无论是什么种类,他都应该返回一个Observable对象。

每个操作符都必须考虑几件事:

  • 返回一个全新的Observable对象
  • 对上下游的订阅和退订处理
  • 处理异常
  • 及时释放资源

关于Hooks的使用(数据请求)

使用Hooks遇到的问题

React 16.8 版本里Hooks已经正式发布,在React相关的生态里也渐渐有不少人开始使用Hooks,由于Hooks具有较大的灵活性,可封装📦的特性,让刚接触并尝试使用Hooks的我遇到了不少的麻烦,也写了特别丑的设计。

关于请求数据的问题

我在使用hooks的时候遇到的第一个问题就是请求的封装,一个组件的数据请求通常发生在组件创建的时候,当我使用来写这个组件,代码一开始是下面这个样子的:

import React, { useState } from 'react';
import axios from 'axios';

function App() {
  const [list, setList] = useState([]);
  useEffect(() => {
   	axios('http://xxx.com').then(res => setList(res.data))
  },[]);
  return (
    <div></div>
  );
}
export default App;

我会使用useEffect来让组件在mounted的时候产生副作用,发出数据请求,并传入一个空数组来让组件不会在update的时候再次运行副作用函数。(关于useEffect的使用请阅读官方文档

有时候我们的数据会依赖于某些条件,比如限定一些搜索等等,我们应该在这个依赖条件发生改变的时候去重新请求数据,更新我们的视图。

import React, { useState } from 'react';
import axios from 'axios';

function App() {
  const [list, setList] = useState([]);
  const [query, setQuery] = useState(''); 
  const [search, setSearch] = useState('');

  useEffect(() => {
   	axios.get('http://xxx.com',{params:{search}})
      .then(res => setList(res.data))
  },[search]);
  return (
    <div>
    	 <input 
          type="text" 
          value={query}
          onChange={event => setQuery(event.target.value)}
       />
      <button type="button" onClick={() => setSearch(query)}>
        搜索
      </button>
    </div>
  );
}
export default App;

我们用useState创建另外一个状态来描述我们的搜索条件,query用来表示输入框的内容,search用来表示需要搜索的参数,他们在组件一开始创建的时候同一个空字符,当然,你也可以写不一样的字符,

相比之前代码的区别主要是我们的给useEffect传入的依赖不再是一个空数组,而是我们在运行副作用函数的时候确实需要的依赖search,在这里,我们必须和react约定好,你依赖什么,那就必须诚实的把依赖告诉react,不然总是会产生难以理解的bug。

加入依赖后请求数据的副作用函数不再会只在mounted的时候运行,他同时会在search发生变化的时候运行。

我们知道数据的请求肯定有状态,比如loading/error,所以需要添加请求的状态

import React, { useState } from 'react';
import axios from 'axios';

function App() {
  const [list, setList] = useState([]);
  const [query, setQuery] = useState(''); 
  const [search, setSearch] = useState('');
	const [isLoading, setLoading] = useState(false);
  const [isError, setError] = useState(false);
  useEffect(() => {
    setError(false);
    setLoading(true);
   	axios.get('http://xxx.com',{params:{search}})
      .then(res => setList(res.data))
    	.catch(err => setError(true))
    	.finally(() => setLoading(false))
  },[search]);
  return (
    <div>
    	 <input 
          type="text" 
          value={query}
          onChange={event => setQuery(event.target.value)}
       />
      <button type="button" onClick={() => setSearch(query)}>
        搜索
      </button>
    </div>
  );
}
export default App;

以上的代码我们为数据请求添加了状态,这样我们就可以让视图在不同的状态下显示不同的UI,让用户可以感知到数据正在请求。

当我写到这里的时候,我发现这种写法并不合理,我们完全可以把数据请求的状态和过程封装抽离出我们的组件,毕竟这和我们的组件毫无关系,使用react的自定义hooks这个概念,可以帮助我们抽离hooks代码。

const useFetchData = (fetchUrl='',initList=[],initSearch='') => {
  const [list, setList] = useState(initList);
  const [search, setSearch] = useState(initSearch);
	const [isLoading, setLoading] = useState(false);
  const [isError, setError] = useState(false);
   useEffect(() => {
    setError(false);
    setLoading(true);
    let didCancel = false;
   	axios.get(fetchUrl,{params:{search}})
      .then(res => {
      	setList(res.data)
	    })
    	.catch(err => {
  			setError(true)    	
	    })
    	.finally(() => {
  			setLoading(false)    
	    })
     return () => {
       didCancel = true;
     }
  },[search]);
  return {list,search,isLoading,isError,setSearch}
}

function App() {
  const [query, setQuery] = useState(''); 
	const {
    		 list,
         isLoading,
         isError,
         setSearch
  			} = useFetchData('http://xxx.com')
  return (
    <div>
    	 <input 
          type="text" 
          value={query}
          onChange={event => setQuery(event.target.value)}
       />
      <button type="button" onClick={() => setSearch(query)}>
        搜索
      </button>
    </div>
  );
}

这时会发现自定义的Hooks返回的状态和函数有点臃肿,这时候我们可以使用useReduce来代替useState,让我们的结构更加的清晰。

你可以在上述代码中发现我们的自定义Hooks里有个didCancel变量,这是在为了解决异步回调的问题,在react中有很多时候异步回调会发生在组件已经卸载的情况下,didCancel可以解决这个问题,由于useEffect的特性,在search发生变化的时候,异步的回调也应该取消运行,didCancel也可以满足这种需求。如果你不太了解为什么,可以先去阅读react相关api的文档,并阅读关于hooks的caputer value的特性,这个特性是我们在书写hooks的时候需要注意的地方,算是一个不小的门槛。

🙏谢谢阅读。

参考文档:

https://www.robinwieruch.de/react-hooks-fetch-data/

https://reactjs.org/docs/hooks-reference.html#useeffect

https://overreacted.io/zh-hans/a-complete-guide-to-useeffect/

AJAX重定向和跨域

HTTP 203 重定向发生的跨域问题

具体场景是一个Ajax请求在 203 的情况下, Header上有location字段,这代表该接口被打重定向到这个地址,这个时候浏览器会有一些默认的操作:

  1. ajax 是一个为了局部更新页面而产生的一个 api ,所以当一个ajax 在 203 的时候,并不会默认去跳转到 location 页面,因为这是一个局部的更新。
  2. 浏览器在收到203后,会去确认是否有location字段,如果有这个字段,那么会再去请求这个地址,也是一个http请求,但是之前203的ajax 的回调并不会立马运行,而会等待新建的请求结束,再去判断是否success或则error
  3. 当新的请求失败的时候,回去运行error 回调,成功则跳转。

然而问题是部分业务下location的页面会发生跨域的问题。

后来通过把ajax 请求切换为form 提交的方式去试用浏览器默认的跳转行为,来避开跨域的问题。

网络模型基础

为什么了解网络

身为一名前端工程师,也许你会认为我们并不需要对网络有所了解,但是我们却经常接触网络这一块的技术,来看看我们经常接触的网络的具体应用。

  • http协议
  • socket
  • 页面缓存
  • http长链接
  • 请求跨域
  • 文件的上传和下载
  • 直播的推流和拉流

从上面众多技术的应用可以看到,网络对于前端来说是不可或缺的部分,如果我们不了解网络相关的知识,当我们遇到某些问题的时候,可能就会完全找不到解决方案。

网络的基础模型

本篇文章并不会太深入的去讲解网络的底层知识(毕竟我这个菜鸡也不太了解😢)

  1. 物理层 (纯物理的传输)
  2. 链路层(帧的形式传输)
  3. 网络层 (通过IP对2个节点之间连接)
  4. 传输层(建立主机间的联系 TCP / UDP / TLS / SSl)
  5. 会话层(建立和管理会话)
  6. 表示层(数据的编码和转换)
  7. 应用层(http,https)

上面是网络相关文章都会提到的网络七成模型,我们可以看到http是基于tcp协议的,经过ssl加密包装之后就是https协议。

但是TCP的连接建立是有一个具体的过程的,它和UDP的建立并不一样,我们经常听到的三次握手,四次挥手,这个经典的问题就是TCP连接的建立方案,只有TCP成功的建立,我们才能在TCP的基础上去包装成HTTP,所以HTTP是在应用层面的协议。那么让我们来具体看看TCP的建立。

image-20190814170848612

上面就是三次握手的图示,下面则是四次挥手

image-20190814170904104

经过三次握手,我们才能确认2个节点之间建立了一个稳定的连接,我们大致了解下TCP连接的建立,之所以是三次才能建立一个稳定的连接是因为只有这样才能以最少的次数建立稳定的连接,让我们来继续了解HTTP吧

HTTP

我们现在都知道前端与服务器的交互大多数是依赖HTTP了,那么究竟怎么才能创建一个HTTP连接呢,在浏览器中,浏览器为我们提供了一个叫做XHR的构造函数,通过这个函数,我们就可以去创建一个HTTP请求,当然,我们在实际运用的过程中并不会直接去使用XHR,而是用一些封装出来的开源库,来方便的进行请求。

我们在开发过程中,经常回去调试面板观察后端返回的数据是否正确,在这个面板上我们可以查看详细的HTTP报文,可以一般都是回去观察这些内容

  • Request Header

  • Response Header

  • params

    这些通常是我们用来检查自己创建的http链接是否正确的途径,通过它我们一般就可以确定出BUG出现在哪里,那么我们就应该去了解这一部分的知识,

Header和params

每一个Http请求都会有一个头部信息,用来描述请求的信息,服务端一般也会去检查请求的头部信息,有时会根据不同的头部信息去返回不同的数据,

每个Http的返回同样会带上一些头部信息,不过这些头部信息一般不需要我们进行处理和读取,浏览器会自动去处理这些头部信息,比如存储cookie和缓存页面和跨域控制等,这些通常不是我们来进行控制的,当然,你也需要去了解这些头部信息代表的意思,了解这些可以帮助我们去定位问题。

params一般就是我们发到服务器的数据。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.