Giter Site home page Giter Site logo

blog's Introduction

Anathan "ana" (pronounced ɑːnɑː) Pham is an Australian professional Dota 2 player who is currently playing for OG.

blog's People

Contributors

anathanpham avatar dependabot[bot] avatar

Stargazers

 avatar

blog's Issues

模块化

ES Module

浏览器端的模块化。
你需要在模块内部使用导入导出,意味着你需要在HTML中写<script type="module" src="main.mjs"></script>
它是顶级模块,也是入口模块。模块导入其他模块,可以根据目录结构发起请求。

动态导入

可以将import作为函数来使用。函数调用的返回值为promise。

对TypeScript的思考

最近在读TypeScript的文档,在这里记录自己的思考。

TS受限于ECMAScript

TS无法对解构的变量进行类型声明。因为这在ES中被视为变量的别名。

readonly 的不可靠

TS无视类型字段的readonly,进行兼容。

interface ReadonlyPerson {
  readonly name: string;
  readonly age: number;
}

let writablePerson: Person = {
  name: "Person McPersonface",
  age: 42,
};

// works
let readonlyPerson: ReadonlyPerson = writablePerson;

console.log(readonlyPerson.age); // prints '42'
writablePerson.age++;
console.log(readonlyPerson.age); // prints '43'

readonly的陷阱:

  1. readonly对数组和对象字段的作用不同
  2. TS不兼容数组类型的readonly
let x: readonly string[] = [];
let y: string[] = [];

x = y;
y = x;
The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.

别名(type)的应用

// 泛型 组合
type OrNull<Type> = Type | null;

TS额外的类型

ReadonlyArray

TS泛型

TS一般是类型模型,泛型是对类型模型的抽象(或类型模型的模型)。
因为实际上没有对第二次抽象单独进行具象,所以泛型会导致类型推断丢失。

function loggingIdentity<Type>(arg: Type): Type {
  console.log(arg.length);
Property 'length' does not exist on type 'Type'.
  return arg;
}

类型和值

//You can only use types when indexing, meaning you can’t use a const to make a variable reference:

const key = "age";
type Age = Person[key];
//Type 'any' cannot be used as an index type.
//'key' refers to a value, but is being used as a type here. Did you mean 'typeof key'?
//Try
//However, you can use a type alias for a similar style of refactor:

type key = "age";
type Age = Person[key];

浏览器的event loop

浏览器的event loop遵循下面的顺序(都是可选的,意味着每个步骤不一定执行):

  • 宏任务:取宏任务队列的第一个(最先进队列)执行
  • 微任务:循环执行并清空微任务队列(集合)
  • 渲染:应该渲染(页面产生变化、注册了requestAnimationFrame)并且有渲染机会(硬件刷新率约束和出于性能原因而限制用户代理的因素)
  • 空闲:requestIdleCallback

补充

渲染机会

  • 硬件刷新率,16ms提供一个渲染机会(为什么requestAnimationFrame回调会立即执行)
  • 性能原因,页面在后台等情况

React如何更新Fiber树(虚拟DOM)?

本文只关注更新阶段(Render)的细节,不关注Scheduler(驱动React进行更新是Scheduler的工作)。

从setState开始

为了直观的描述更新的过程,我提供一个具体的场景。

class App extends React.Component {
  state = {
    name: "ana",
  };
  render() {
    const { name } = this.state;
    return React.createElement(
      "h1",
      {
        onClick: () => {
          this.setState({
            name: name + " OG",
          });
        },
      },
      name
    );
  }
}

给原生组件h1绑定点击事件,当点击h1后,看看React会做出什么响应。

通过合成事件,React执行了onClick回调。进而执行了this.setState。setState调用了更新器(注1)。
更新器通过this(App组件实例)获取到App fiber(注2)。

创建更新对象

计算过期时间(注3),根据过期时间和更新payload(payload指更新的实际数据,在这里是{name: name + " OG"}),加上setState第二个参数callback(如果有的话),一起组成了更新对象update。
update会被添加进App fiber的更新队列(updateQueue)(注4)(注5)属性中。

安排更新

React首先会将触发更新的fiber节点自身的过期时间更新(注6),再更新父节点标记(子代过期时间)(注7),表示子孙节点有更新。并且一直向上进行标记,直到所有祖先都被标记。(这是为了避免更新fiber树种未发生变动的其他树枝。

将fiber标记好后,就开始安排更新了。
安排更新前会进行检查,如果更新已经安排了并且新的更新优先级不高于现有优先级,则不安排更新。否则,取消当前task回调,安排新的task。
React通过scheduleCallback安排更新,它根据优先级和回调创建了一个任务(task)。task模拟了浏览器事件循环的宏任务。(对于延迟任务使用setTimeout,对于过期/立即执行任务使用postMessage【注8】)
在这里task的任务的内容是更新fiber树
对于同步任务,React做了特别的处理。React将同步任务的回调收集起来,放入同步队列。

TODO:生产中...

注释

  • 注1:更新器通过依赖注入的得到,在class组件实例生成后注入。因此在constructor中调用setState会报错(实际警告,React只定义了接口,ReactDOM实现了接口)。
  • 注2:App组件对应的fiber,为了方便描述。(注意:这个说法不严谨。因为组件可以重复,fiber是唯一的)
  • 注3:计算过期时间在传统模式(NoMode)下永远是Sync。在Blocking模式下,会根据当前优先级确定过期时间,如果不是立即执行优先级则将过期时间设为Batched(批处理过期时间,优先级低于Sync)。在ConcurrentMode(并发)模式下,可能会得到延迟的过期时间。
  • 注4:updateQueue有多个属性,其中baseQueue是遗留(上一轮更新中,因为优先级不够被跳过)的更新队列,share.pending是下一轮更新的更新队列。
  • 注5:更新队列的数据结构为环链表,fiber保存尾结点(最新的更新对象)的引用。
  • 注6:过期时间代表了该fiber在更新阶段的更新优先级。
  • 注7:准确的说是更新标记(childExpirationTime)。可能fiber本身已经带有标记(过期时间),对比两个标记,取大(优先级更高)的。
  • 注8:postMessage的用法是,通过MessageChannel类构造实例 channel,channel有两个属性port1和port2,它们都是支持发布订阅模式的对象。port1发布消息,port2订阅后会收到消息。订阅消息的回调函数是宏任务。

弱声明式?

弱声明式,我取得名字。指的是介于声明式和命令式之间的一种代码形态。
实现上按照声明式去做,但是技术局限,包含了很多命令式的代码。
换句话说,代码没规划好。

用unstable_batchedUpdates批处理异步中的setState

React中调用setState如果当前没有上下文,那么在安排更新函数中,会同步刷新任务队列,即会立即更新state,组件树重新渲染。如果多次调用setState,则会导致组件树多次渲染。
使用unstable_batchedUpdates,为setState调用栈外层包上一层上下文。则多次setState调用只会使组件树渲染一次。

认识preact

简单的看了文档之后,
我理解的是 包含了react和简化版reactDom的集合。

但是它不是React,我想知道差异在哪里、体现到实际使用的差异?

跨域

CORS

通过设置Access-Control系列的响应头,主要是Access-Control-Allow-Origin

JSONP

服务器返回的数据形如
doSomeThing({name:'ana',age:24}),实际上服务器向提供给客户端的数据是{name:'ana',age:24}
客户端需要定义doSomeThing方法。

代理

本地运行前后端分离项目时使用。

postMassage

向指定域名传递消息

class中的箭头函数

在React写class组件时,有这样一种写法

class Button extends React.component {
 handleClick = ()=>{
  this.setState()
 }
 handleClick2(){
  // do something
 }
}

对于handleClick2,它是Button的原型(prototype)上的方法。
当使用这个方法的时候存在一个陷阱,如果调用该方法时,不是通过当前组件实例调用,可能出现this的指向问题。

针对上面的陷阱,我们会使用handleClick这种写法。
那它是怎么做到的呢?在JavaScript的世界,没有魔法。这种写法是一种语法糖,他会被babel转换。
如果你来做,你会怎么实现呢?
我设想将handleClick也作为prototype上的方法行吗?
显然,这样this会指向调用者。没有将this的指向固定。所以不可行。
将this固定很容易的想到的是bindcallapply
固定this的工作在constructor中完成。
还有另一种办法,它没有改变this的指向,而是利用闭包,将this作为私有变量。同样的也在constructor中完成。

constructor(){
 const that = this
 this.handleClick = function(){
  // ... handleClick的原始代码 将this替换为that 
 }
}

babel采用的是第二种办法。

在React中,我们会利用@babel/plugin-proposal-class-properties来使用这种语法。

另外,class语法本身支持箭头函数实例属性的语法。
它和普通对象字面量行为不一致。
待补充

迭代器

迭代协议

迭代协议分为两种,可迭代协议迭代器协议
可迭代协议用于for of,一个对象或原型上有[Symbol.iterator],此时它是可迭代对象。[Symbol.iterator]的值为返回值为迭代器的函数(它可以是生成器也可以是普通函数)。for of迭代的项是生成器.next调用的返回值.value

迭代器协议
通常迭代器也实现了可迭代协议。

var myIterator = {
    next: function() {
        // ...
    },
    [Symbol.iterator]: function() { return this }
}

接收可迭代对象的API

new Map([iterable])
new WeakMap([iterable])
new Set([iterable])
new WeakSet([iterable])

Promise.all(iterable)
Promise.race(iterable)
Array.from(iterable)
展开语法

内置可迭代对象

String、Array、TypedArray、Map 和 Set,它们的原型对象都实现了 @@iterator 方法。

ReactDOM.render做了什么?

ReactDOM.render将React元素挂载到DOM容器上。

一些疑问

  • 组件等类型的React元素没有DOM实体,是如何挂载?
  • React元素只是一个对象,它的子节点如何获得?
  • 第三个参数--回调函数,何时执行?
  • 注水?SSR?

源码分析

ReactDOM.render挂载React元素中,最核心是构建虚拟DOM树。

如果当前的DOM容器不是React控制(注1),且不是服务端渲染(注2)。React会清空容器DOM(注3)。
然后,React创建了虚拟DOM的全局状态存储对象--FiberRootNode,和顶级(根)Fiber节点--current FiberNode(注4)。

此时,有了根fiber。下一步就从根fiber开始构建fiber树。React构建fiber树的逻辑,沿用了fiber树更新的逻辑。
React巧妙的将React元素(注5)作为更新对象添加到current更新队列,将ReactDOM.render作为更新对象的回调(注6)。
然后安排更新(注7),同步(注8)执行更新fiber树

注释

  • 注1:DOM带有特殊的属性,值为current FiberNode。
  • 注2:服务端渲染的页面存在DOM结构,而不是只有一个容器DOM。
  • 注3:容器中原有的DOM是意外的DOM。
  • 注4:current FiberNode是对应fiber的父节点。
  • 注5:并不是React元素,而是值为React元素的表达式。这里是为了直观说明。
  • 注6:这表示更新对象完成更新后回调将会执行,即根fiber完成挂载后将执行,回调的this执行 fiber的实体stateNode
  • 注7:安排更新,将更新当前fiber的过期时间(越大优先级越高),遍历当前fiber的祖先节点,更新他们的子代过期时间(用来告诉父节点,它的孩子需要更新)。
  • 注8:因为传统模式过期时间总是Sync,同时挂载时上下文为非批处理

Git tag如何清除?

多个本地仓库拉取了远程仓库包含tag的git记录。
此时远程仓库删除tag如何同步给所有本地仓库?
我这里好像本地仓库推送代码会将tag重新添加到远程仓库。
tag有变更记录吗?

node.js执行shell命令

const process = require('child_process');
process.exec('shutdown -h now',function (error, stdout, stderr) {
 console.log(123)
})

快速排序

目标

  1. 实现一个快速排序算法
  2. 稳定排序、原地排序
  3. 提供接口

快速排序的原理

对数组的左右两端用指针标记,取出数组最左侧的元素作为参照数,将数组中小于参照数的置于左侧、大于参照数的置于右侧,此时数组被分为左右两个子数组,再分别对两个子数组进行上述排序,直到子数组长度为1或0。

实现的难点

如何处理元素与参照数相等的情况
空槽和当前方向
一轮排序结束后,参照数是否分组

代码实现

下面的排序函数只是按照最初的思路进行实现,后面会进行优化。

function quickSort(arr) {
  if (arr < 2) {
    return arr;
  }
  const length = arr.length;
  let left = 0;
  let right = length - 1;
  const reference = arr[0];
  let slotInLeft = true;

  // 假设排序结果为递增数组
  // 5. 最后的情况是,两个指针距离为1,一个指向待比较元素,一个指向空槽。
  // 5.1 待比较元素较大,放入空槽,原始空槽指针移动到原始待比较元素位置(新空槽),两指针相遇。
  // 5.2 待比较元素较小,它的指针移动到空槽,两指针相遇。
  // 所以一轮排序的终止条件为,两指针相遇(即left===right)(排序中的状态:left的值应该小于right)
  while (left !== right) {
    // 6. 对空槽位置不同情况的判断
    if (slotInLeft) {
      // 1. 比较参考数和待排序元素
      if (reference > arr[right]) {
        // 2. 对待排序元素排序,放入空槽
        arr[left] = arr[right];
        // 3. 更新空槽标记,空槽从左侧变为右侧 (一开始默认空槽在左侧,但是漏掉了对空槽位置不同情况的判断[6])
        slotInLeft = false;
        // 4. 开始下一次比较,左侧索引left已经排好序,继续从左向右扫描(此时发现需要添加**扫描的终止条件**)
        left++;
      } else {
        // 右侧值大,保持不动,继续从右向左扫描
        right--;
      }
    } else {
      // 7. 当前空槽在右侧,待排序元素则在左侧。此时需要特别注意,在空槽在左侧的情况没有对“与参考数相等”的情况做处理(相当于默认相等相等时,右侧元素保持不动),所以在此必须处理这种情况,左侧元素相等时将移动到右侧
      if (reference <= arr[left]) {
        arr[right] = arr[left];
        slotInLeft = true;
        right--;
      } else {
        left++;
      }
    }
  }
  // 8. 一轮排序结束后,left和right相遇。当前left指针指向空槽,将参照数放入空槽。
  arr[left] = reference;
  // 9. 以left为界限,划分两个子数组。特别注意,右子数组可能存在数组越界的问题(当left指向arr最右侧时,left+1会越界,越界长度为1)
  const leftChildArr = arr.slice(0, left);
  const rightChildArr = left === length - 1 ? [] : arr.slice(left + 1, length);
  // 10. 分别对左子数组和右子数组排序,然后组合起来,整个数组就排好序了
  return [...quickSort(leftChildArr), reference, ...quickSort(rightChildArr)];
}

Redux-saga

saga是如何工作的

saga作为中间件,原理是对dispatch的增强。
saga拦截了action,现在saga中进行处理,之后再抛给redux。

yield的作用是什么

在saga,yield的作用是让saga来处理任务。如果没有yield,saga不会去处理任务。

聪明的call

call不光可以调用请求,还可以调用saga。他会阻塞saga任务直到它本身完成它其中的异步saga也完成其中的异步其实没说清楚,异步中的异步也会阻塞,下面用代码来表示。可以方向的确保被call的saga任务完全完成,包括嵌套的异步saga任务。

export function* asyncSage1() {
  console.log("asyncSage1");
  yield fork(request1);
  yield fork(asyncSage2);
  yield delay(1000);
}
export function* asyncSage2() {
  yield fork(request2);
  console.log("asyncSage2 end");
}
export function request1() {
  return new Promise((res, rej) => {
    setTimeout(() => {
      res();
      console.log("request 1");
    }, 3000);
  });
}
export function request2() {
  return new Promise((res, rej) => {
    setTimeout(() => {
      res();
      console.log("request 2");
    }, 6000);
  });
}
export function* watchIncrementAsync() {
  yield takeEvery("asyncSage1", asyncSage1);
}

React合成事件?

react合成事件指的是react用js模拟了一个Dom事件流。(fiber树模拟Dom树结构)
合成事件的事件流在fiber树中发生捕获和冒泡。

从点击输入框开始

当你点击input输入框,react在根节点(注1)监听到focus事件(注2)(注3)。

如何从原生事件找到对应的虚拟Dom?

此时,react得到的信息只有原生事件对象(nativeEvent)。react通过nativeEvent对应的Dom(eventTarget),沿着Dom树向上找到距离该eventTarget最近的被react管理的Dom节点(注4)(注5),并获得对应的fiber A。

接着通过事件插件(注6),创建合成事件(注7)A。合成事件A被react视为模拟事件流中的事件源,fiber A被react视为事件目标。

合成事件流?

从fiber A出发向上(直到顶层fiber HostComponent为止)收集所有的host类型的fiber。
然后将收集到的fiber数组,从后向前(捕获),再从前向后(冒泡)遍历。每次遍历,会收集(注8)当前遍历项fiber节点的绑定的focus事件。之后(事件插件完成后,即合成事件生成好了)会按照收集的顺序执行foucs回调。
react就是这样模拟了事件流。

扩展

点击输入框引起多个事件

除了focus外,也触发了其他的事件--click等。react在根节点对不同类型的事件进行了监听,每监听到一种事件就会派发一次,多种类型的事件,会派发多次。
点击输入框,会先后触发focus和click。当focus事件的派发完后,就会派发click事件。
每次某个事件派发结束,会处理待处理的同步任务队列(flushSyncCallBackQueue)。

意料之外的render?

在NoMode模式下,多次派发事件且每个事件都改变了状态(如调用setState),则对应组件会被render多次。
在本例中,点击input输入框,如果给input绑定了focus事件和click事件并且事件回调都调用setState,input将会render两次。

为什么react中的input设置value后成为受控组件?

react中,在input设置了value属性的条件下,无论在输入框中输入什么,输入框的值都不会改变。除非你改变了input组件的state。
react在处理完模拟事件流后,会调用方法将一些意外的效果重置。
例如该场景,在input中输入了一个值,input输入框会出现你输入的值,但马上input的值会被对应的fiber的value属性更新(finishEventHander 重置受控组件)。
如果没有给input设置value则会忽略。

为什么合成事件属性有时无法访问?

因为react在事件流(捕获到冒泡)完后就将合成事件对象释放(SyntheticEvent.prototype.destructor 将合成事件对象的属性重置)。

react如何模拟事件的阻止冒泡阻止默认行为

react按照事件流顺序执行回调,在执行前会检查当前合成事件对象是否处于阻止冒泡的状态,如果是,则终止事件流。
react的合成事件对象原型对原生函数进行增强。对原生事件方法的阻止冒泡阻止默认行为进行了封装(内部也调用原生事件的方法)。

注释

注1:根节点,在 react-v16 为 document ,在 react-v17 为挂载容器Dom
注2:focus事件并不是冒泡事件,react对非冒泡事件在捕获阶段监听
注3:根节点对所有事件进行了监听,除了特别例外submit、reset、invalid和媒体事件等
注4:react将fiber树挂载到Dom树上时,每个宿主(host)类型fiber节点与Dom节点一一对应,并链接
注5:向上查找是因为可能子Dom节点并不是被react管理的,如第三库滚动插件等
注6:为了模拟Dom事件,react进行的补充
注7:react内部一个构造函数的实例,合成事件的部分属性来源于nativeEvent。合成事件与fiber关联
注8:收集而不是执行。因为react针对某一类型事件的做了批处理

控制反转和依赖注入的区别?

  • 控制反转是一种在软件工程中解耦合的**,调用类只依赖接口,而不依赖具体的实现类,减少了耦合。 控制权交给了容器,在运行的时候才由容器决定将具体的实现动态的“注入”到调用类的对象中。

  • 依赖注入是一种设计模式,可以作为控制反转的一种实现方式。

webpack带sourceMap debug

step into next function call进入的时候会闪一下,这时是 webpack 加载目标模块的依赖。

相较于普通的发布订阅模式,promise的好处?

相较于普通的发布订阅模式,promise可以保存状态。
这意味着promise不需要依赖加载顺序。
如果是发布订阅模式,订阅者还没有加载(添加订阅回调),当发布者发布该订阅者会错过这是执行订阅回调的机会。

git常用命令

别名

git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status

什么时候使用try...catch?

原则:可平稳退化、可接受的错误,不应该导致程序中断

缓存时

期望缓存请求数据,通过sessionStorage缓存。
可能存在缓存溢出导致报错,这是可接受的错误,不应该导致程序中断。

Fiber如何更新?

在盘古开天地的时候,只有一个fiber。这个fiber就是current的顶级fiber。
此后,当安排更新,便向从current顶级fiber复制(TODO:?)了一个fiber,称为workInProgress fiber。
然后就开始了父亲带着孩子的更新之旅。

TLDR;

  1. 两个父亲进场,更新workInProgress父亲。
  2. 比较新儿子和旧儿子。如果旧儿子和新儿子不是一类人,就把旧儿子家拆掉,断绝旧亲戚的关系。如果是一类人就把旧儿子赶走,新儿子搬进来住。
  3. 新儿子变成了父亲,作为父亲出征,一代代的如此反复。

beginWork

第一次对比这两个顶级fiber,他们一模一样,没有什么可比较的。对于父亲,只更新。
过程是获取fiber上的 update对象,把update应用到workInProgress fiber上。这时,workInProgress fiber是最新的。
最新的父亲,生的孩子也是最新的。所以新父亲生下来新孩子,而旧父亲,只能眼巴巴的看着,但旧父亲有旧孩子。
此时,差异出现了。新孩子(react元素)和旧孩子(fiber)。(那父亲们呢?父亲已经更新了。对比的目的是什么?是为了得到最新的fiber树,同时尽可能利用旧的fiber节点。对于两个顶级父亲,他们是相同类型的,所以复用并更新。)

如果新孩子和旧孩子类型相同,那么就复用。
对于类型相同的孩子,新的和旧的,区别在于props。(createElement(类型,props,...children),children实际是props的子属性)
怎么复用呢?新孩子复制旧孩子的属性,只更新props(props在新父亲生下新孩子的时候已经确定)。
此后,在下一轮。新孩子和旧孩子,摇身一变,都成了父亲。(除了pendingProps几乎一样,他们都是旧的。更新对象update还挂在身上)

如果新孩子和旧孩子类型不同。
新孩子和旧孩子不是一类人了,旧孩子和新孩子不在一家(同一个节点)住。旧孩子的亲人(sibling兄弟节点、child叶子节点),家具(state或hook、update对象)和新孩子没有任何关系了。
那么就把旧孩子删掉(标记删除,此时属于render阶段。最终的删除操作在commit阶段执行)。
新孩子,转化成的fiber的alternate属性为null。
下一轮,新孩子变身成workInProgress父亲,null代表current父亲。

渲染性能优化

如何流畅的将10万个DOM渲染到页面中?

  1. 大数量级的问题,分治。
  2. 异步解决阻塞。更好的办法,使用requestAnimationFrame。
  3. 代码实现,闭包,状态机。
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <!-- 测试DOM -->
    <button id="button">begin render by 100 elements per frame!</button>
    <button id="button2">begin render by 1000 elements per frame !</button>
    <button id="button3">begin render by 10000 elements per frame !</button>
    <button id="button4">begin render by sync !</button>
    <!-- 容器DOM -->
    <div id="container">container</div>
    <!-- 核心代码 -->
    <script>
      const waitRenderData = Array.from({ length: 10 * 10000 })
      const container = document.querySelector("#container")

      // 部分渲染 这不是一个幂等函数
      function renderPart(
        container,
        waitRenderData,
        startIndex,
        option = {
          size: 100,
        }
      ) {
        const SIZE = option.size
        const endIndex = Math.min(startIndex + SIZE, waitRenderData.length - 1) //指向最后一个待渲染的数据
        const fragmentEl = document.createDocumentFragment()
        while (startIndex <= endIndex) {
          // 创造节点,初始化节点,装载节点
          const textEl = document.createElement("text")
          textEl.innerText = "" + startIndex + "-"
          fragmentEl.appendChild(textEl)
          // 更新循环条件
          startIndex++
        }
        // 最终 startIndex 指向第一个**待渲染**的数据(下一轮)

        container.appendChild(fragmentEl)
        return startIndex
      }

      // 渲染 启动
      function renderBigData(container, waitRenderData, option) {
        let indexPoint = 0 //指向第一个**待渲染**的数据
        let startTime = Date.now()
        let endTime = startTime

        indexPoint = renderPart(container, waitRenderData, indexPoint, option)
        // 自动渲染
        autoRenderPart = () => {
          indexPoint = renderPart(container, waitRenderData, indexPoint, option)
          if (indexPoint > waitRenderData.length - 1) {
            endTime = Date.now()
            console.log("渲染耗时:" + (endTime - startTime) / 1000 + "秒")
            return
          }
          requestAnimationFrame(autoRenderPart)
        }
        requestAnimationFrame(autoRenderPart)
      }
    </script>

    <!-- 测试代码 -->
    <script>
      const button = document.querySelector("#button")
      const button2 = document.querySelector("#button2")
      const button3 = document.querySelector("#button3")
      const button4 = document.querySelector("#button4")
      button.addEventListener("click", runTestCode.bind(null, 100))
      button2.addEventListener("click", runTestCode.bind(null, 1000))
      button3.addEventListener("click", runTestCode.bind(null, 10000))
      button4.addEventListener(
        "click",
        runTestCode.bind(null, waitRenderData.length)
      )
      function runTestCode(size) {
        container.innerHTML = ""
        renderBigData(container, waitRenderData, { size })
      }
    </script>
  </body>
</html>

React Context原理?

Context是通过React.createContext创建的特殊对象,它的两个属性Provider、Consumer都是对象类型的React组件。

什么是Provider组件 ?

Provider组件可将props.value传递给所有它的子节点。
子节点可以通过静态属性接收(class组件)、Consumer组件的render children接收。

接收的原理是什么?

class组件在constructor执行构造实例之前,已经获得了context,并且构造时将context作为第二个参数传入constructor。
class组件父类对实例属性进行赋值,其中包含this.context。

  function Component(props, context, updater) {
    this.props = props;
    this.context = context;
    this.refs = emptyObject;
    this.updater = updater || ReactNoopUpdateQueue;
  }

补充一句,在获得context时顺便将当前fiber和context关联起来(作为依赖),目的是为了接收祖先Provider组件改变context发出的通知。

Consumer组件的组件类型的一个属性就是context的引用,所以可以直接获取context。把context作为参数传给render child。

Provider的value改变会发生什么?

Provider的value如果直接修改其属性,是不会有反应的。它遵循react的更新机制,需要通过react的更新(如setState)来触发引起Provider的更新。
render阶段,更新Provider中如果value发生改变(Object.is),Provider会遍历所有子节点,找到依赖该上下文的节点并标记这些fiber的更新时间。这相当于这些节点内部发生了更新(类似于调用setState(null))。

补充一句,为class组件额外生成并添加一个强制更新的更新对象,它将覆盖shouldUpdate的效果。

value没有改变发生什么?

Provider会检查props.children是否相同,如果相同则跳过Provider更新返回旧的子fiber。否则,对比孩子(新的props.children和旧子fiber)。

收敛离散的状态

一个封装的模块,内部各处和其他模块通信,通信产生副作用。
单独处理显然是不合理。
需要将离散的逻辑,收集起来。
在哪里收集?
在哪通信就在哪收集。
在模块销毁等时候,集中处理。

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.