onclick 源码_精读:手写React框架 解析Hooks源码

08ccdc59bd822a552de267ba84136186.png

写在开头:

去年发表过一篇手写React,带diff算法,异步setState队列的文章,有一位阿里的朋友在下面评论,让我可以用hooks实现一次,也很简单,我当时觉得,这人有病,现在回过头来看,还是补上吧,世间万物都逃不过真香定律


往期精彩文章:

原创:从零实现一个简单版React (附源码)

如何优化你的超大型React应用 【原创精读】

深度:手写一个WebSocket协议    [7000字]

干货:深入了解React 渲染原理及性能优化

精读:10个案例让你彻底理解React hooks的渲染逻辑


我之前写的React gitHub地址是:

https://github.com/JinJieTan/mini-react/tree/diff-async

仓库分支 从master => diff => diff-async 逐步变得完善

581f06689d3fc28f0df5214475577893.png

目前hooks刚开始实现,在我的React源码中,每个入口中预留了hooks

import handleAttrs from './handleAttrs';import { createComponent, setComponentProps } from '../components/utills';const ReactDom = {};//传入虚拟dom节点和真实包裹节点,把虚拟dom节点通过_render方法转换成真实dom节点,然后插入到包裹节点中,这个就是react的初次渲染const render = function(vnode, container) {  return container.appendChild(_render(vnode));};ReactDom.render = render;export function _render(vnode) {  console.log(vnode);  console.log('_render ');  if (vnode === undefined || vnode === null || typeof vnode === 'boolean')    vnode = '';  if (typeof vnode === 'number') vnode = String(vnode);  if (typeof vnode === 'string') {    let textNode = document.createTextNode(vnode);    return textNode;  }  if (typeof vnode.tag === 'function') {    //hooks    const component = createComponent(vnode.tag, vnode.attrs);    setComponentProps(component, vnode.attrs);    return component.base;  }  // vnode= {tag,props,children}  // {tag:"li",attrs:{xxx:},children:1}  const dom = document.createElement(vnode.tag);  if (vnode.attrs) {    Object.keys(vnode.attrs).forEach(key => {      const value = vnode.attrs[key];      handleAttrs(dom, key, value);//如果有属性,例如style标签、onClick事件等,都会通过这个函数,挂在到dom上    });  }  vnode.children && vnode.children.forEach(child => render(child, dom)); // 递归渲染子节点  return dom;}export default ReactDom;

要开始完善hooks部分了我们现在,从25行代码开始

我们先把启动入门的文件换成hook

import React from './react';import ReactDom from './reactDom';import App from './app';import Hook from './hook';ReactDom.render(, document.querySelector('#root'));

开发一个简单的hook组件

import React from 'react';export default function hook(props) {  console.log(props, 'props');  return <div>hooksdiv>;}

然后启动项目

parcel index.html

然后访问 http://localhost:1234

发现什么都没有,因为之前对hooks并没有做什么处理

export function createComponent(component, props) {  console.log(component, props, '1');  let inst;  // 如果是类定义组件,则直接返回实例  if (component.prototype && component.prototype.render) {    inst = new component(props);    // 如果是函数定义组件,则将其扩展为类定义组件  } else {    inst = new Component(props);    inst.constructor = component;    inst.render = function () {      return this.constructor(props);    };  }  console.log(inst.render(), 'render');  return inst;}

我们现在,这里发现如果是一个函数式组件,那么就构造调用它,然后确保原型之后,扩展成类组件。

这里15行代码断点日志可以看到,已经可以获得虚拟DOM了

812c4b8bc5cc0c8029b0beaa704de1d3.png

继续回到入口这里

  if (typeof vnode.tag === 'function') {    //hooks    const component = createComponent(vnode.tag, vnode.attrs);    setComponentProps(component, vnode.attrs);    return component.base;  }

在将函数组件拓展成类组件后,进行setComponentProps的一些操作,最终返回真实dom,即component.base,被插入到root节点中,完成渲染

export function renderComponent(component) {  //dom  console.log('renderComponent',component);  let base;  //返回虚拟dom对象 调用render方法,会用到state 此时的state已经通过上面的队列更新了  const renderer = component.render();  if (component.base && component.componentWillUpdate) {    component.componentWillUpdate();  }  if (component.base && component.shouldComponentUpdate) {    let result = true;    result =      component.shouldComponentUpdate &&      component.shouldComponentUpdate(        (component.props = {}),        component.newState      );    if (!result) {      return;    }  }  //得到真实dom对象  base = diffNode(component.base, renderer);  console.log(base,'base')  if (component.base) {    if (component.componentDidUpdate) component.componentDidUpdate();  } else {    component.base = base;    base._component = component;    component.componentDidMount && component.componentDidMount();    return;  }  //挂载真实的dom对象到对应的 组件上 方便后期对比  component.base = base;  //挂载对应到组件到真实dom上 方便后期对比~  base._component = component;}

这里我们可以看到,hook组件传入后的打印:

609c87df5bc958c6a213d784f29932d2.png

base是undefined,因为我们目前没有对hook的diff这些做处理,所以真实dom为undefined,这样屏幕上没有任何元素显示

由于hook的逻辑跟class组件实现逻辑是不太一样,里面很多是依赖链表、数组去实现的,所以我们针对这个地方,要单独写一套逻辑,如果是hook组件的话,我们要重新做一套解析

 if (typeof vnode.tag === 'function') {    //hooks    const component = createComponent(vnode.tag, vnode.attrs);    const isHook = true;    setComponentProps(component, vnode.attrs, isHook);    return component.base;  }

在这里我们加入isHook字段,传入。这样后续就知道我们是hook组件了,应该如何对待

如下所示:

a1f6c3f256621dac3f74fc8965eeac2f.png

这里的核心是diff算法实现,之前我是把真实dom节点和虚拟dom节点去对比的:

  //得到真实dom对象  base = diffNode(component.base, renderer);

这里我们今天先把它渲染出来,然后实现useState这些核心的东西。下一期我再加入diff算法,这样阅读也更友好(主要是太晚了,明天还要上班)

2f9071466ca2b54400b97dc446d9edc3.png

此时发现,是可以得到我的tag标签,以及children内容,那么就可以展示了

我只用了十行不到的代码就实现了hooks组件展示

aff04cc85997bdc18b73f8d333f7e468.png

达到预期(虽然目前没有预期,递归diff、展示等)

9e01ad2b18e803f7a8f3a4dc5d20b994.png

接下来先看看React怎么实现的useState。

hooks里,有一个getHookState函数,会在当前组件的实例上挂载__hooks属性。__hooks为一个对象,__hooks对象中的_list属性使用数组的形式,保存了所有类型hooks(useState, useEffect…………)的执行的结果,返回值等。因为_list属性是使用数组的形式存储状态,所以每一个 hooks 的执行顺序尤为重要。


function getHookState(index) {  if (options._hook) options._hook(currentComponent);  // 检查组件,是否有__hooks属性,如果没有,主动挂载一个空的__hooks对象  const hooks =    currentComponent.__hooks ||    (currentComponent.__hooks = {      _list: [], // _list中存储了所有hooks的状态      _pendingEffects: [], // _pendingEffects中存储了useEffect的state      _pendingLayoutEffects: [], // _pendingLayoutEffects中存储了useLayoutEffects的state      _handles: []    });  // 根据索引index。判断__hooks._list数组中,是否有对应的状态。  // 如果没有,将主动添加一个空的状态。  if (index >= hooks._list.length) {    hooks._list.push({});  }  // 返回__hooks._list数组中,索引对应的状态  return hooks._list[index];}

hook的执行指针

// 当前hooks的执行顺序指针let currentIndex;// 当前的组件的实例let currentComponent;let oldBeforeRender = options._render;// vnode是options._render = vnode => {  if (oldBeforeRender) oldBeforeRender(vnode);  // 当前组件的实例  currentComponent = vnode._component;  // 重置索引,每一个组件hooks state list从0开始累加  currentIndex = 0;  if (currentComponent.__hooks) {    currentComponent.__hooks._pendingEffects = handleEffects(      currentComponent.__hooks._pendingEffects    );  }};

我需要模仿它,用链表实现useState的效果,由于useState在源码中其实是依赖useReducer实现,这点在.d.ts源码可以看到

092a8ba609fe2b61f53f0f484ddef69f.png

// useState接受一个初始值initialState,初始化statefunction useState(initialState) {  return useReducer(invokeOrReturn, initialState);}

useState基于useReducer实现,invokeOrReturn是一个简单工具函数

function invokeOrReturn(arg, f) {  return typeof f === "function" ? f(arg) : f;}

useReducer:

function useReducer(reducer, initialState, init) {  // currentIndex自增一,创建一个新的状态,状态会存储在currentComponent.__hooks._list中  const hookState = getHookState(currentIndex++);  if (!hookState._component) {    // state存储当前组件的引用    hookState._component = currentComponent;    hookState._value = [      // 如果没有指定第三个参数`init, 返回initialState      // 如果指定了第三个参数,返回,经过惰性化初始值的函数处理的initialState      // `useState`是基于`useReducer`的封装。      // 在`useState`中,hookState._value[0],默认直接返回initialState      !init ? invokeOrReturn(null, initialState) : init(initialState),      // hookState._value[1],接受一个`action`, { type: `xx` }      // 由于`useState`是基于`useReducer`的封装,所以action参数也可能是一个新的state值,或者state的更新函数作为参数      action => {        // 返回新的状态值        const nextValue = reducer(hookState._value[0], action);        // 使用新的状态值,更新状态        if (hookState._value[0] !== nextValue) {          hookState._value[0] = nextValue;          // ⭐️调用组件的setState, 重新进行diff运算(在Preact中,diff的过程中会同步更新真实的dom节点)          hookState._component.setState({});        }      }    ];  }  // 对于useReduer而言, 返回[state, dispath]  // 对于useState而言,返回[state, setState]  return hookState._value;}

‍‍

正式开始:

import { Component } from '../components/component';import useState from './useState';const React = {};React.Component = Component;export const myUseState = useState;React.createElement = function (tag, attrs, ...children) {  return {    tag,    attrs,    children,  };};export default React;

在React中加入useState模块,第一个版本,做简单点,容易阅读理解上手。一步到位读写都太累

const useState = (initialState) => {  return [value, setValue];};export default useState;

我们在useState使用时,传入初始值,然后返回一个数组,提供后续数组结构赋值使用

const myUseState = (initialState) => {  console.log(this, 'this');  let value = initialState || null;  const setValue = (newValue) => {    value = newValue;  };  return [value, setValue];};export default myUseState;

这个初始版本实现很容易,我们先不考虑其他场景,这个乞丐版,如何跑起来

现在组件内已经可以看到useState了

import React, { myUseState } from './react';export default function hook(props) {  const [value, setValue] = myUseState(1);  console.log(props, 'props', myUseState, this);  return <div>hooksdiv>;}

打印效果:

040cba01bca4eab8bf1816de4e89223d.png

可是如何跟组件重新渲染逻辑挂钩呢?这里可能需要使用到链表了,但是我们是先实现乞丐版,并不追求性能,要求就是能用即可。接下来我会思考,如何将hook和组件内的渲染结合在一起,其实今天是太监了,本来想写完的,太晚了。先思考一个好的设计,五一回来后开始实现Hooks。想要一起开发,PR形式提交代码也可以~

最后

  • 欢迎加我微信(CALASFxiaotan),拉你进技术群,长期交流学习...

  • 欢迎关注「前端巅峰」,认真学前端,做个有专业的技术人...

c4a932b020e32f9019085efd1da9f533.png点个在看支持我吧,转发就更好了

好文我在看?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/512510.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

EMR on ACK 全新发布,助力企业高效构建大数据平台

简介&#xff1a; 阿里云 EMR on ACK 为用户提供了全新的构建大数据平台的方式&#xff0c;用户可以将开源大数据服务部署在阿里云容器服务&#xff08;ACK&#xff09;上。利用 ACK 在服务部署和对高性能可伸缩的容器应用管理的能力优势&#xff0c;用户只需要专注在大数据作业…

华为120hz鸿蒙系统,华为亮剑,120Hz+鸿蒙系统+5500mAh,竟然如此销魂

原标题&#xff1a;华为亮剑&#xff0c;120Hz鸿蒙系统5500mAh&#xff0c;竟然如此销魂随着制造工艺的不断成熟&#xff0c;智能手机也迎来了前所未有的发展&#xff0c;同时这也导致了手机厂商们之间的竞争变得愈发的激烈了。众所周知&#xff0c;华为手机是一个深受普通老百…

云上应用系统数据存储架构演进

简介&#xff1a; 回顾过去二十年的技术发展&#xff0c;整个应用形态和技术架构发生了很大的升级换代&#xff0c;而任何技术的发展都与几个重要的变量相关。本文将会给大家分享应用系统数据架构的演进以及云上的架构最佳实践。 作者 | 木洛 来源 | 阿里技术公众号 一 前言 …

深入解析 Dubbo 3.0 服务端暴露全流程

简介&#xff1a; 随着云原生时代的到来&#xff0c;Dubbo 3.0 的一个很重要的目标就是全面拥抱云原生。正因如此&#xff0c;Dubbo 3.0 为了能够更好的适配云原生&#xff0c;将原来的接口级服务发现机制演进为应用级服务发现机制。 作者介绍 熊聘&#xff0c;Github账号pin…

jquery将html转换word,HTML代码转word!亲测!可用!!!

现在项目中遇到一个需求&#xff0c;就是一个富文本编辑区中&#xff0c;有echars表格。用户点击保存按钮&#xff0c;需要导出为word文档。因为现在接手的项目&#xff0c;是基于上一个项目的框架。两个项目功能点差不多。但是在导出word这块&#xff0c;是后台java做的。也就…

智能搜索推荐一体化营收增长解决方案

简介&#xff1a; 图数据库GDB提供智能搜索推荐一站式服务&#xff0c;基于达摩院的智能搜索推荐算法和知识图谱技术&#xff0c;助力企业快速过渡冷启动过程&#xff0c;面向业务场景定制化方案&#xff0c;以提升核心业务指标&#xff0c;实现业务营收增长。 方案架构 方案特…

Redis 使用 List 实现消息队列的利与弊

作者 | 码哥字节 来源 | 码哥字节 分布式系统中必备的一个中间件就是消息队列&#xff0c;通过消息队列我们能对服务间进行异步解耦、流量消峰、实现最终一致性。 目前市面上已经有 RabbitMQ、RochetMQ、ActiveMQ、Kafka等&#xff0c;有人会问&#xff1a;“Redis 适合做消息队…

阿里云表格存储全面升级,打造一站式物联网存储新方案

简介&#xff1a; 阿里云表格存储全面升级&#xff0c;打造一站式物联网存储新方案 2021年9月1日&#xff0c;阿里云表格存储Tablestore重磅发布新能力&#xff1a;一站式物联网存储IoTstore。该新能力是阿里云表格存储Tablestore面向物联网深度垂直场景进行的一次技术升级&am…

手把手一起 图形化安装 k8s 集群

作者 | 小碗汤来源 | 我的小碗汤今天接着上一节&#xff0c;使用 KuboardSpray 图形化安装kubernetes集群[1]&#xff0c;记录了安装时可能遇到的问题。对此项目感兴趣的同学&#xff0c;不妨亲手实践一下~以下记录了安装单节点&#xff08;单master的集群&#xff09;&#xf…

Jaeger插件开发及背后的思考

简介&#xff1a; 本文主要介绍Jaeger最新的插件化后端的接口以及开发方法&#xff0c;让大家能够一步步的根据文章完成一个Jaeger插件的开发。此外SLS也推出了对于Jaeger的支持&#xff0c;欢迎大家试用。 随着云原生 微服务的推广和落地&#xff0c;服务监控也变得越来越重…

基于 MySQL + Tablestore 分层存储架构的大规模订单系统实践-架构篇

简介&#xff1a; 本文简要介绍了基于 MySQL 结合 Tablestore 的大规模订单系统方案。这种方案支持大数据存储、高性能数据检索、SQL搜索、实时与全量数据分析&#xff0c;且部署简单、运维成本低。 作者 | 弘楠 来源 | 阿里技术公众号 一 背景 订单系统存在于各行各业&#…

ajax返回来总是html,ajax返回类型

基于arcgis的webgis开发中目前是否还直接用ajax技本人是arcgis刚接触者&#xff0c;以前有听说过ajax这个技术&#xff0c;用于浏览器和web服务ajax技术现在依然是客户端浏览器和服务器交互的重要手段。 如果你用arcgis api for js技术&#xff0c;同样会使用ajax技术。这是良好…

三分钟教你用 Scarlet 写一个 WebSocket App

作者 | Eason来源 | 程序员巴士在移动应用程序中&#xff0c;数据层是屏幕上显示内容的真实来源。然而&#xff0c;在今年早些时候在 Tinder 中集成了 WebSocket API 时&#xff0c;维护它成为了一个令人头疼的问题。为了在 Android 上更轻松地集成 WebSocket&#xff0c;Scarl…

重磅发布|新一代云原生数据仓库AnalyticDB「SQL智能诊断」功能详解

简介&#xff1a; AnalyticDB For MySQL为用户提供了高效、实时、功能丰富并且智能化的「SQL智能诊断」和「SQL智能调优」功能&#xff0c;提供用户SQL性能调优的思路、方向和具体的方法&#xff0c;降低用户使用成本&#xff0c;提高用户使用ADB的效率 SQL是一种简单易用的业…

技术干货|基于Apache Hudi 的CDC数据入湖「内附干货PPT下载渠道」

简介&#xff1a; 阿里云技术专家李少锋(风泽)在Apache Hudi 与 Apache Pulsar 联合 Meetup 杭州站上的演讲整理稿件&#xff0c;本议题将介绍典型 CDC 入湖场景&#xff0c;以及如何使用 Pulsar/Hudi 来构建数据湖&#xff0c;同时将会分享 Hudi 内核设计、新愿景以及社区最新…

探究 Java 应用的启动速度优化

简介&#xff1a; 在高性能的背后&#xff0c;Java 的启动性能差也令人印象深刻&#xff0c;大家印象中的 Java 笨重缓慢的印象也大多来源于此。高性能和快启动速度似乎有一些相悖&#xff0c;本文将和大家一起探究两者是否可以兼得。 作者 | 梁希 高性能和快启动速度&#x…

阿里云刘伟光:金融核心系统将步入分布式智能化的时代

1月18日&#xff0c;阿里云在京发布金融核心系统转型“红宝书”&#xff0c;并推出“金融级云原生工场”&#xff0c;通过新的建设理念和相应的全链路平台技术&#xff0c;以及先进的部署体系&#xff0c;支撑金融机构建设面向未来的新一代分布式智能化核心系统。 阿里云智能新…

5分钟搞定Loki告警多渠道接入

简介&#xff1a; Loki是受Prometheus启发的水平可扩展、高可用、多租户日志聚合系统。用户既可以将Loki告警直接接入SLS开放告警&#xff0c;也可以先将Loki接入Grafana或Alert Manager&#xff0c;再借助Grafana或Alert Manager实现Loki间接接入SLS开放告警。 直接接入 您可…

当微服务遇上 Serverless | 微服务容器化最短路径,微服务 on Serverless 最佳实践

简介&#xff1a; 阿里云Serverless应用引擎&#xff08;SAE&#xff09;初衷是让客户不改任何代码&#xff0c;不改变应用部署方式&#xff0c;就可以享受到微服务K8sServerless的完整体验&#xff0c;开箱即用免运维。 前言 微服务作为一种更灵活、可靠、开放的架构&#x…

学计算机就业靠谱吗,2018年计算机专业就业怎么样?

由孙中山先生创办的至今已有一百多年办学传统&#xff0c;已经成为一所国内一流、国际知名的现代综合性大学。涉足的领域较广&#xff0c;有法律、医学等领域&#xff0c;每个领域都取得不俗的成绩。该校的计算机专业自开设以来也颇受学生欢迎&#xff0c;2018年计算机专业就业…