React中常见的TypeScript定义实战教程

目录
  • 一 引沿
  • 二 什么是调和
  • 三 什么是Filber
  • 四 实现调和的过程
  • 五 总结

一 引沿

Fiber 架构是React16中引入的新概念,目的就是解决大型 React 应用卡顿,React在遍历更新每一个节点的时候都不是用的真实DOM,都是采用虚拟DOM,所以可以理解成fiber就是React的虚拟DOM,更新Fiber的过程叫做调和,每一个fiber都可以作为一个执行单元来处理,所以每一个 fiber 可以根据自身的过期时间expirationTime,来判断是否还有空间时间执行更新,如果没有时间更新,就要把主动权交给浏览器去渲染,做一些动画,重排( reflow ),重绘 repaints 之类的事情,这样就能给用户感觉不是很卡。

二 什么是调和

调和是一种算法,就是React对比新老虚拟DOM的过程,以决定需要更新哪一部分。

三 什么是Filber

Fiber的目的是为了让React充分利用调度,以便做到如下几点:

  • 暂停工作,稍后再回来
  • 优先考虑不同类型的工作
  • 重用以前完成的工作
  • 如果不再需要,则中止工作

为了实现上面的要求,我们需要把任务拆分成一个个可执行的单元,这些可执行的单元就叫做一个Fiber,一个Fiber就代表一个可执行的单元。

一个Fiber就是一个普通的JS对象,包含一些组件的相关信息。

function FiberNode(){
  this.tag = tag;                  // fiber 标签 证明是什么类型fiber。
  this.key = key;                  // key调和子节点时候用到。
  this.type = null;                // dom元素是对应的元素类型,比如div,组件指向组件对应的类或者函数。
  this.stateNode = null;           // 指向对应的真实dom元素,类组件指向组件实例,可以被ref获取。

  this.return = null;              // 指向父级fiber
  this.child = null;               // 指向子级fiber
  this.sibling = null;             // 指向兄弟fiber
  this.index = 0;                  // 索引

  this.ref = null;                 // ref指向,ref函数,或者ref对象。

  this.pendingProps = pendingProps;// 在一次更新中,代表element创建
  this.memoizedProps = null;       // 记录上一次更新完毕后的props
  this.updateQueue = null;         // 类组件存放setState更新队列,函数组件存放
  this.memoizedState = null;       // 类组件保存state信息,函数组件保存hooks信息,dom元素为null
  this.dependencies = null;        // context或是时间的依赖项

  this.mode = mode;                //描述fiber树的模式,比如 ConcurrentMode 模式

  this.effectTag = NoEffect;       // effect标签,用于收集effectList
  this.nextEffect = null;          // 指向下一个effect

  this.firstEffect = null;         // 第一个effect
  this.lastEffect = null;          // 最后一个effect

  this.expirationTime = NoWork;    // 通过不同过期时间,判断任务是否过期, 在v17版本用lane表示。

  this.alternate = null;           //双缓存树,指向缓存的fiber。更新阶段,两颗树互相交替。
}

type 就是react的元素类型

export const FunctionComponent = 0;       // 对应函数组件
export const ClassComponent = 1;          // 对应的类组件
export const IndeterminateComponent = 2;  // 初始化的时候不知道是函数组件还是类组件
export const HostRoot = 3;                // Root Fiber 可以理解为跟元素 , 通过reactDom.render()产生的根元素
export const HostPortal = 4;              // 对应  ReactDOM.createPortal 产生的 Portal
export const HostComponent = 5;           // dom 元素 比如 <div>
export const HostText = 6;                // 文本节点
export const Fragment = 7;                // 对应 <React.Fragment>
export const Mode = 8;                    // 对应 <React.StrictMode>
export const ContextConsumer = 9;         // 对应 <Context.Consumer>
export const ContextProvider = 10;        // 对应 <Context.Provider>
export const ForwardRef = 11;             // 对应 React.ForwardRef
export const Profiler = 12;               // 对应 <Profiler/ >
export const SuspenseComponent = 13;      // 对应 <Suspense>
export const MemoComponent = 14;          // 对应 React.memo 返回的组件

比如元素结构如下:

export default class Parent extends React.Component{
   render(){
     return <div>
       <h1>hello,world</h1>
       <Child />
     </div>
   }
}

function Child() {
  return <p>child</p>
}

对应的Filber结构如下:

有了上面的概念后我们就自己实现一个Fiber的更新机制

相关React实战视频讲解:进入学习

四 实现调和的过程

我们通过渲染一段jsx来说明React的调和过程,也就是我们要手写实现ReactDOM.render()

const jsx = (
  <div className="border">
    <h1>hello</h1>
    <a href="https://www.reactjs.org/" rel="external nofollow" >React</a>
  </div>
)

ReactDOM.render(
  jsx,
  document.getElementById('root')
);

1. 创建FiberRoot

react-dom.js

function createFiberRoot(element, container){
    return {
    type: container.nodeName.toLocaleLowerCase(),
    props: { children: element },
    stateNode: container
  }
}
function render(element, container) {
  const FibreRoot = createFiberRoot(element, container)
  scheduleUpdateOnFiber(FibreRoot)
}
export default { render }

2. render阶段

调和的核心是render和commit,本文不讲调度过程,我们会简单的用requestIdleCallback代替React的调度过程。

ReactFiberWorkloop.js

let wipRoot = null // work in progress
let nextUnitOfwork = null // 下一个fiber节点

export function scheduleUpdateOnFiber(fiber) {
  wipRoot = fiber
  nextUnitOfwork = fiber
}

function workLoop(IdleDeadline) {
  while(nextUnitOfwork && IdleDeadline.timeRemaining() > 0) {
    nextUnitOfwork = performUnitOfWork(nextUnitOfwork)
  }
}

function performUnitOfWork() {}

requestIdleCallback(workLoop)

每一个 fiber 可以看作一个执行的单元,在调和过程中,每一个发生更新的 fiber 都会作为一次 workInProgress 。那么 workLoop 就是执行每一个单元的调度器,如果渲染没有被中断,那么 workLoop 会遍历一遍 fiber 树

performUnitOfWork 包括两个阶段:

  • 是向下调和的过程,就是由 fiberRoot 按照 child 指针逐层向下调和,期间会执行函数组件,实例类组件,diff 调和子节点
  • 是向上归并的过程,如果有兄弟节点,会返回 sibling兄弟,没有返回 return 父级,一直返回到 fiebrRoot

这么一上一下,构成了整个 fiber 树的调和。

import { updateHostComponent } from './ReactFiberReconciler'
function performUnitOfWork(wip) {
  // 1. 更新wip
  const { type } = wip
  if (isStr(type)) {
    // type是string,更新普通元素节点
    updateHostComponent(wip)
  } else if (isFn(type)) {
    // ...
  }

  // 2. 返回下一个要更新的任务 深度优先遍历
  if (wip.child) {
    return wip.child
  }
  let next = wip
  while(next) {
    if (next.sibling) {
      return next.sibling
    }
    next = next.return
  }
  return null
}

根据type类型区分是FunctionComponent/ClassComponent/HostComponent/...
本文中只处理HostComponent类型,其他类型的处理可以看文末的完整代码链接。

ReactFiberReconciler.js

import { createFiber } from './createFiber'

export function updateHostComponent(wip) {
  if (!wip.stateNode) {
    wip.stateNode = document.createElement(wip.type);
    updateNode(wip.stateNode, wip.props);
  }
  // 调和子节点
  reconcileChildren(wip, wip.props.children);
}

function reconcileChildren(returnFiber, children) {
  if (isStr(children)) {
    return
  }

  const newChildren = isArray(children) ? children : [children];
  let previousNewFiber = null
  for(let i = 0; i < newChildren.length; i++) {
    const newChild = newChildren[i];
    const newFiber = createFiber(newChild, returnFiber)

    if (previousNewFiber === null) {
      returnFiber.child = newFiber
    } else {
      previousNewFiber.sibling = newFiber
    }
    previousNewFiber = newFiber
  }
}

function updateNode(node, nextVal) {
  Object.keys(nextVal).forEach((k) => {
    if (k === "children") {
      if (isStringOrNumber(nextVal[k])) {
        node.textContent = nextVal[k];
      }
    } else {
      node[k] = nextVal[k];
    }
  });
}

createFiber.js

export function createFiber(vnode, returnFiber) {
  const newFiber = {
    type: vnode.type,   // 标记节点类型
    key: vnode.key,     // 标记节点在当前层级下的唯一性
    props: vnode.props, // 属性
    stateNode: null,    // 如果组件是原生标签则是dom节点,如果是类组件则是类实例
    child: null,        // 第一个子节点
    return: returnFiber,// 父节点
    sibling: null,      // 下一个兄弟节点
  };

  return newFiber;
}

至此已经完成了render阶段,下面是commit阶段,commit阶段就是依据Fiber结构操作DOM

function workLoop(IdleDeadline) {
  while(nextUnitOfwork && IdleDeadline.timeRemaining() > 0) {
    nextUnitOfwork = performUnitOfWork(nextUnitOfwork)
  }

  // commit
  if (!nextUnitOfwork && wipRoot) {
    commitRoot();
  }
}

function commitRoot() {
  commitWorker(wipRoot.child)
  wipRoot = null;
}

function commitWorker(wip) {
  if (!wip) {
    return
  }
  // 1. 提交自己
  const { stateNode } = wip
  let parentNode = wip.return.stateNode
  if (stateNode) {
    parentNode.appendChild(stateNode);
  }

  // 2. 提交子节点
  commitWorker(wip.child);

  // 3. 提交兄弟节点
  commitWorker(wip.sibling);
}

五 总结

  • Fiber结构,Fiber的生成过程。
  • 调和过程,以及 render 和 commit 两大阶段。

到此这篇关于React中常见的TypeScript定义实战的文章就介绍到这了,更多相关React TypeScript定义内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 如何用webpack4.0撸单页/多页脚手架 (jquery, react, vue, typescript)

    1.导语 首先来简单介绍一下webpack:现代 JavaScript 应用程序的 静态模块打包工具 .当 webpack 处理应用程序时,它会在内部构建一个会映射项目所需的每个模块 的依赖图(dependency graph),并生成一个或多个 bundle .webpack4.0出现之后,我们可以不用再引入一个配置文件来打包项目,并且它仍然有着很高的可配置性,可以很好满足我们的需求. 在开始正文之前,首先先来看看我们要实现的成果: 支持ES6+JQuery+Less/Scss的单页/多页脚手

  • React+TypeScript+webpack4多入口配置详解

    资源 React-16.8.* react-router-dom-4.3.* TypeScript-3.5.* webpack-4.* eslint-5.16.* 项目目录 ├── dist # 打包结果目录 │ ├── demo1 //类别demo1的打包结果 │ │ ├── demo1.himl │ │ ├── demo1.js │ │ └── demo1.css │ └── demo2 ... //类别demo2的打包结果 ├── src # 业务资源文件目录 │ ├── category

  • TypeScript在React项目中的使用实践总结

    序言 本文会侧重于TypeScript(以下简称TS)在项目中与React的结合使用情况,而非TS的基本概念.关于TS的类型查看可以使用在线TS工具

  • React项目中应用TypeScript的实现

    目录 一.前言 二.使用方式 无状态组件 有状态组件 受控组件 三.总结 一.前言 单独的使用typescript 并不会导致学习成本很高,但是绝大部分前端开发者的项目都是依赖于框架的 例如和vue.react 这些框架结合使用的时候,会有一定的门槛 使用 TypeScript 编写 react 代码,除了需要 typescript 这个库之外,还需要安装@types/react.@types/react-dom npm i @types/react -s npm i @types/react-

  • 为react组件库添加typescript类型提示的方法

    以我自己的组件react-better-countdown为例, 首先在package.json里面添加types: types/index.d.ts, , 然后文件目录上添加对应文件夹和文件, 最后是index.d.ts文件的编写,具体看代码: import * as React from 'react'; interface CountdownProps { count?: number; dayText?: string | React.ReactElement; hourText?: s

  • React+Typescript实现倒计时Hook的方法

    首先对setInterval做了Hook化封装

  • React中常见的TypeScript定义实战教程

    目录 一 引沿 二 什么是调和 三 什么是Filber 四 实现调和的过程 五 总结 一 引沿 Fiber 架构是React16中引入的新概念,目的就是解决大型 React 应用卡顿,React在遍历更新每一个节点的时候都不是用的真实DOM,都是采用虚拟DOM,所以可以理解成fiber就是React的虚拟DOM,更新Fiber的过程叫做调和,每一个fiber都可以作为一个执行单元来处理,所以每一个 fiber 可以根据自身的过期时间expirationTime,来判断是否还有空间时间执行更新,如

  • react中常见hook的使用方式

    1.什么是hook? react hook是react 16.8推出的方法,能够让函数式组件像类式组件一样拥有state.ref.生命周期等属性. 2.为什么要出现hook? 函数式组件是全局当中一个普通函数,在非严格模式下this指向window,但是react内部开启了严格模式,此时this指向undefined,无法像类式组件一样使用state.ref,函数式组件定义的变量都是局部的,当组件进行更新时会重新定义,也无法存储,所以在hook出现之前,函数式组件有很大的局限性,通常情况下都会使

  • 那些项目中常见的TypeScript错误总结

    目录 引言 1.TS2456 2.TS2554 3.TS1169 4.TS2345 5.TS2589 6.TS2322 总结 引言 TypeScript 错误信息由错误码和详细信息组成.例如:TS2456 ,其中错误码是以“TS”开头 + 数字(一般是 4 位数字)结尾这样的格式组成的字符串,用来作为特定类型错误的专属代号.下面我们看一下那些常⻅,但在官方文档甚少提及的类型错误: 1.TS2456 首先是由于类型别名循环引用了自身造成的 TS2456 类型错误,如下示例: type T = Re

  • React中常见的动画实现的几种方式

    现在,用户对于前端页面的要求已经不能满足于实现功能,更要有颜值,有趣味.除了整体 UI 的美观,在合适的地方添加合适的动画效果往往比静态页面更具有表现力,达到更自然的效果.比如,一个简单的 loading 动画或者页面切换效果不仅能缓解用户的等待情绪,甚至通过使用品牌 logo 等形式,默默达到品牌宣传的效果. React 作为最近几年比较流行的前端开发框架,提出了虚拟 DOM 概念,所有 DOM 的变化都先发生在虚拟 DOM 上,通过 DOM diff 来分析网页的实际变化,然后反映在真实 D

  • Vue和React中快速使用Electron的简单教程

    目录 提示: 前言 一.前提 二.在Vue和React中快速使用 1. 安装Electron 2. 运行成功后,打包成软件 总结 提示: 本篇文章只是教你简单上手,Electron本身功能是非常强大的,这里边的结合,只是能让你简单实现一个桌面应用程序!如需要复杂的配置electron,建议去参考官网~ 前言 对于我们来说Electron相当于一个壳子,可以把写好的网页程序嵌入到壳子里面,可以运行在桌面上的一个程序,可以把网页打包成一个在桌面应用程序,也类似我们平时做的套壳打包的5+app,简单来

  • TypeScript定义接口(interface)案例教程

    接口的作用: 接口,英文:interface,其作用可以简单的理解为:为我们的代码提供一种约定. 在Typescript中是这么描述的: TypeScript的核心原则之一是对值所具有的结构进行类型检查.它有时被称做"鸭式辨型法"或"结构性子类型化". 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约. 举个例子: // 定义接口 Person interface Person { name: string; age: numb

  • React中refs的一些常见用法汇总

    目录 什么是Refs 一.String 类型的 Refs 二.回调 Refs 三.React.createRef() 四.useRef 五.Refs 与函数组件 总结 什么是Refs Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素. Ref转发是一项将ref自动通过组件传递到子组件的技巧. 通常用来获取DOM节点或者React元素实例的工具.在React中Refs提供了一种方式,允许用户访问dom节点或者在render方法中创建的React

  • vue项目中使用ts(typescript)入门教程

    目录 1.引入Typescript 2.配置文件webpack配置 3.让项目识别.ts 4.vue组件的编写 data()中定义数据 props传值 完整代码案例 最近项目需要将原vue项目结合ts的使用进行改造,这个后面应该是中大型项目的发展趋势,看到一篇不错的入门教程,结合它并进行了一点拓展记录之.本文从安装到vue组件编写进行了说明,适合入门. 1.引入Typescript npm install vue-class-component vue-property-decorator --

  • React中的Hooks进阶理解教程

    目录 Hooks进阶 useState - 回调函数的参数 useEffect - 清理副作用 useEffect - 发送网络请求 useRef useContext Hooks进阶 useState - 回调函数的参数 使用场景 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略.如果初始 state 需要通过计算才能获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用 语法 const [name, setName] = useState(()=>{

  • Go泛型实战教程之如何在结构体中使用泛型

    目录 01 目标 02 实现 01 目标 假设我们要实现一个blog系统,在该系统中有以下两个结构体: type Category struct { ID int32 Name string Slug string } type Post struct { ID int32 Categories []Category Title string Text string Slug string } 为了提高系统的性能,我们需要实现一个缓存系统,该缓存可以用于缓存各种类型,在该示例中我们限定为只能缓存

随机推荐