react 学习(一) 实现简版虚拟 dom 和挂载
楼主最近入职新单位了,恰好新单位使用的技术栈是 react
,因为之前一直进行的是 vue2/vue3
和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试的也都能答出来。但对于 react
只是有一定的了解,没有真实的学习过实现,虽然之前也看过一些文章,但是只停留在表面,因为别人这么写了,也就下意识的认为是这样。本次正好配合工作的契机,我们从零开始学习一下,使用的话呢就简单一过,相信大家也都用过或者看完官网也都了解了。如果您是大佬,欢迎批评指正;如果您是初级选手,希望能够一起学习。
初始化项目
我们借助脚手架实现开发环境,内部使用的库用自己开发的。
- npx create-react-app react-dome1 (当然也可以全局安装脚手架)
public
目录只留下index.html
,src
目录下只留下index.js
- 修改 scripts 命令
我们需要使用旧的转换方式,这样我们可以自己实现createElement
方法
// cross-env 需要自己安装
scripts": {"start": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts start","build": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts build","test": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts test","eject": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts eject"
},
react 17
引入了新的jsx
转换特性,因为之前的开发,即使页面中未直接使用React
,但是也要引入,这是因为babel
在转译之后会触发React.createElement
,如果不引入会报错,但是引入了可能也会触发eslint
的报错,引入但是未使用。新特性可以单独使用JSX
而无需引入React
。
新特性一些好处
- 使用全新的转换,你可以单独使用
JSX
而无需引入React
。 - 根据你的配置,
JSX
的编译输出可能会略微改善bundle
的大小。 - 它将减少你需要学习
React
概念的数量,以备未来之需
之前的转换方式
import React from 'react';function App() {return <h1>Hello World</h1>;
}
====================================
import React from 'react';function App() {return React.createElement('h1', null, 'Hello world');
}
新特性转换方式
function App() {return <h1>Hello World</h1>;
}
==================================
// 由编译器引入(禁止自己引入!)
import {jsx as _jsx} from 'react/jsx-runtime';function App() {return _jsx('h1', { children: 'Hello world' });
}
实现 React.createElement
我们先看下原生 createElement
的返回结果
// src/index.js
import React from 'react'
const jsx = <h1 className='title' style={{color: 'red'}}>hello</h1>
console.log(jsx)
我们看到返回了对象,几个重要属性,$$typeof
, props
, type
。我们实现下自己的 createElement
函数。
定义类型常量
// src/constants.js// react 内的元素都是这个类型
export const REACT_ELEMENT = Symbol("react.element");
// react 文本类型
export const REACT_TEXT = Symbol("react.text");
实现 createElement
// src/react.js
// 这三个参数是 babel 解析完,调用React.createElement 传入的,从第三个参数开始都是儿子
function createElement(type, config, children) {if(config) {// 这里可写可不写,就是为了简化下我们自己写的,只把必要的返回,没用的参数越少越清晰嘛delete config.__sourcedelete config.__self}const props = {...config}if (arguments.length > 3) {// 有多个儿子props.chidlren = Array.prototype.slice.call(arguments, 2)} else if (argument.length === 3) {// 只有一个子,直接赋值props.children = children}return {$$typeof: REACT_ELEMENT,type,props}
}const React = {createElement
}
export default React
这里也可以
...children
形式,判断只要判断children
长度就可以了,但是属于es6
的用法,我们按照源码实现
实现 toVdom 辅助函数
我们这里还要进行一下处理,因为如果是文本类型的话,直接就是字符串了,没有类型这种标识了,所以我们要对 children
进行一下包裹,也为了后面的 diff
。
// src/utils.js// 统一规范,方便 diff
export function toVdom(element) {return typeof element === "string" || typeof element === "number"? { // 字符串包裹$$typeof: REACT_ELEMENT,type: REACT_TEXT,props: element,}: element;
}
修改 createElement
函数,包裹儿子节点
...
props.children = Array.prototype.slice.call(arguments, 2).map(toVdom);
...
props.children = toVdom(children);
调用我们自己的实现,我们可以得到如下结果
页面挂载
我们引入 react-dom
,看下原生渲染
import React from "react";
import ReactDOM from "react-dom";let jsx = (<h1 className="title" style={{ color: "red", backgroundColor: "pink" }}>hello<span>111</span></h1>
);
ReactDOM.render(jsx, document.getElementById("root"));
实现 reactDOM.render
大家可以按我写的第几步阅读,基本都做了注视
// 做了两件事
// 1. 虚拟dom变真实dom
// 2. 挂载
function render(vdom, container) { //。 第二步//1 const newDOM = createDOM(vdom) // 不同功能写在不同函数里,清晰 // 第三步//2container.appendChild(newDOM)
}// 创建真实 dom
function createDOM(vdom) {let {type, props} = vdom // 我们知道虚拟dom就是我们生成的那个对象let dom // 最后要返回的if (type === REACT_TEXT) {// 如果是个文本dom = document.createTextNode(props)} else {// 标签节点dom = document.createElement(type)}// 需要对props 中的 style 和 children 和其他进行处理if(props) {// 单独处理属性updateProps(dom, {}, props) // 第四步// 单独处理 chidlrenif(props.chidlren && typeof props.children === 'object' && props.chidlren.$$typeof) {// 文本render(props.chidlren, dom)} else if (Array.isArray(props.children)) {// 子为数组,把子挂载到当前的父 domreconcileChildren(props.children, dom) // 第五步} }return dom
}// 子虚拟节点,父真实节点
function reconcileChildren(chidlrenVdom, parentDom) {// 循环递归处理, 算法题里非二叉树的多子树节点,也是 for 循环遍历处理for (let i = 0; i < childrenVdom.length; i++) {render(childrenVdom[i], parentDOM);}
}// 对 dom 进行新属性赋值,旧属性没有的删除, vue中也是类型的操作,遍历新属性,旧属性
function updateProps(dom, oldProps, newProps) {for(let key in newProps) {if (key === 'children') {continue // 单独处理} else if (key === 'style') {let styleObj = newProps[key]for(let attr in styleObj) {dom.style[attr] = styleObj[attr] }} else {dom[key] = newProps[key]}}// 老的有,新的没有 删除for(let const key in oldProps) {if (!newProps.hasOwnProperty(key)) {delete dom[key]}}
}// 根据调用,返回的一定是对象 第一步
const ReactDOM = {render
}
export default ReactDOm
在入口文件使用我们自己的方法
// src/index.js
import React from "./react";
import ReactDOM from "./react-dom";
可以看到,也实现了渲染
本篇就介绍到这里,我们了解了虚拟 dom
的对象形式,了解了如果挂载到页面上,下一节我们学习下类组件和函数组件的实现,如果有不对,欢迎指正!
react 学习(一) 实现简版虚拟 dom 和挂载相关推荐
- 初识react(二) 实现一个简版的html+redux.js的demo
回顾 初识react(一) 揭开jsx语法和虚拟DOM面纱 初识react(二) 实现一个简版的html+redux.js的demo 初识react(三)在 react中使用redux来实现简版计数器 ...
- 手把手教你React(一)JSX与虚拟DOM
初衷 学习React有一段时间了, 一直想找个时间写一个React的系列文章.忙里抽闲,完成了第一篇.写这系列文章的初衷是总结这段时间的技术学习,以及给那些想学习React的同学们一点帮助.我会尽量以 ...
- 深入理解React(一)JSX与虚拟DOM
初衷 使用 React 有一段时间了, 一直想找个时间写一个 React 的系列文章.忙里抽闲,完成了第一篇.写这系列文章的初衷是总结这段时间的技术学习,以及给那些想学习 React 的同学们一点帮助 ...
- React 入门与实战-课时7 虚拟DOM的本质和目的
DOM树的概念: 一个网页呈现的过程: 1.浏览器请求服务器获取页面HTML代码 2.浏览器先在内存中,解析DOM结构,并在浏览器内存中,渲染出一颗DOM树: 3.浏览器把DOM树,呈现到页面上: R ...
- 【React】通过jsx语法创建虚拟DOM动态及数组赋值的方法(图文+代码)
jsx语法规则: 1.定义虚拟DOM时,不要写引号. 2.标签中混入JS表达式时要用{}. 3.样式的类名指定不要用class,要用className. 4.内联样式,要用style={{key:va ...
- 从 React 历史的长河里聊虚拟 DOM 及其价值
什么是虚拟 DOM 本质上是 JavaScript 对象,这个对象就是更加轻量级的对 DOM 的描述. 对,就是这么简单! 就是一个复杂一点的对象而已,没什么好说的,重点是为什么要有这个东西,以及有了 ...
- 深度学习实战16(进阶版)-虚拟截图识别文字-可以做纸质合同和表格识别
大家好,我是微学AI,今天给大家带来一个关于虚拟截图识别文字的应用,可以运用在多个领域. 案例主要结合Mediapipe手势识别模型,识别出手势的21个关键点的坐标,计算机的摄像头根据食指的坐标信息获 ...
- 前端学习(3196):虚拟dom和真实dom
- 前端学习(3195):虚拟dom的创建方式1的js
最新文章
- 60颗卫星被五手火箭送上天!马斯克疯狂的卫星互联网计划不远了
- 计算机也可以看“视频”,理解“视频”
- 64 大小_电脑系统32位和64位有什么区别
- Spring 3整合Quartz 2实现定时任务--转
- JS 枚举型变量操作
- 来,和腾讯一起共建未来城市
- java epoll select_最新阿里、拼多多、快手Java岗面试题269 道送答案
- php购物车内物品删除,求助 购物车 用session删除 列表的一条
- extjs xtype 类型
- linux系统外接硬盘_如何使用外部硬盘安装linux系统?
- 连接游戏服务器网络延迟高,玩游戏网络延迟高怎么办 网络卡Ping值很高的解决方法...
- “九个字、一只手、专有云”,有孚网络的云上之路
- 网络打印协议之LPR或RAW
- 微信开发工具button跳转页面_微信小程序按钮点击跳转页面详解
- android百度地图路线查询,Android百度地图——路线规划搜索
- php代码托管平台,程序员必须知道的几个Git代码托管平台
- boss是董卓的java游戏_武将列传世界BOSS董卓-殒命长安攻略
- s2系列——s2-012,s2-013,s2-015,s2-016
- python流量监控脚本
- 我的小情绪、小失落、小想念