不积跬步,无以至千里;不积小流,无以成江海
⚙️为什么要造轮子
- 你是否用了很久react,依然只停留于会用react框架上
- 你是否看了很多遍react源码解析的文章,对react的了解依然半知半解
- 你是否在开发中,遇到react的问题,都不知如何下手分析,导致无法精准定位问题
- 你是否看每次想要下定决心开始学习react源码时,看到它那数万行代码,心里就开始打退堂鼓,想着明日再说🙊
明日复明日,明日何其多。蓦然回首,你依然停留在原地。
看百遍,不如做一遍
通过本系列文章的学习,
- 你将学会打造一个相对完整的react,从而让你对react的底层原理有更深入的了解
- 你将学会使用TDD的方式编程
为什么是TDD
如果你问我有什么方法可以让你的编码能力有极大的提升?🦄
那我一定推荐你去了解TDD,很多人对TDD都有偏见,认为TDD太麻烦,增加了工作量,本来写业务代码就已经花费很多时间了,还要花时间去写测试代码,白给自己增加工作量。但事实上,我认为通过TDD的方式编程其实是更高效的。这是因为大部分人遇到一个需求,还没想清楚就开始动手(想一想面试时候的你遇到手写代码题是否是这样),而不是使用以终为始的思维方式去解决问题。
- 明确结果: 定义&理解问题
- 拆解结果:拆解问题
- 给出解决方案
TDD刚好是符合这种思维模式。先写测试代码,也就是先明确结果,明确该需求要达到的目标效果。接着,我们根据这个结果,去拆解任务。最后,编写实现代码,给出解决方案。这整个过程既高效又能让我们写出安全的代码。
学会使用TDD的方式编程,能帮助你使用健壮的代码去解决具体的问题~
现在我们开始吧,本系列文章从0开始循序渐进,从hello,world启程,带你一步一步实现一个工业级react~🐯
🐣效果
本篇文章将打你实现最简单的react,它支持最简单的虚拟dom树的渲染,先来看下最终的效果吧~
// 下载项目到本地
git clone git@github.com:murphywuwu/7-days-react.git
// 切换到hello-world目录
cd examples/hello-world
// 安装依赖
yarn install
// 启动项目
yarn start
代码如下:
分析
现在我们来拆解是如上效果是如何实现的?
阅读你本篇文章,一个简单的hello, world,是如何通过React渲染出来的? ,可以知道渲染出hello,world的整个过程分为如下3步
而react主要负责实现后2步,生成react元素以及渲染react元素(虚拟dom)
创建项目
react源码是一个monorepo结构,接下来,我们来创建一个monorepo项目,目录结构如下所示:
// 创建项目
mkdir 7-days-react
// 初始化项目
lerna init
// 创建包
lerna create react
lerna create react-dom
// 配置lerna.json
{
"packages": ["packages/*"],
"npmClient": "yarn",
"useWorkspaces": true,
"version": "0.0.0"
}
// 配置根目录上的package.json
"workspaces": [
"packages/*"
]
// 安装依赖
lerna add jest --dev
// 配置scripts
// react/package.json, react-dom/package.json
"scripts": {
"test": "jest",
"test:cov": "jest --coverage",
"test:watch": "jest --watch --testTimeout 30000"
}
🍜启程
生成react元素
正如前面说的,现在我们先明确自己想要的结果。打开react.test.js文件,编写如下所示的测试代码
import React from 'React'
descripe('createElement', () => {
it('hello world', function() {
// 创建react元素(虚拟dom树)
const result = React.createElement('h1', {className: 'greeting'}, 'hello, world')
// 明确我们想要的结果
expect(result).to.be({
props: {
children: 'hello world',
},
type: 'h1',
})
})
});
// 在packages/react目录下运行
yarn test:watch
保存代码,测试结果如下
现在,我们来拆解任务,根据测试结果和测试代码我们可以知道
第一:我们需要为react添加createElement方法
第二:我们需要返回一个对象,这个对象也叫react元素或虚拟dom
接着,打开packages/lib/react.js文件编写业务代码:
// lib/react.js
module.exports = {
// 第一步:为react添加createElement方法
createElement: function (type, props, children) {
// 第二步:返回react元素
return {
type,
props: {
children,
...props,
},
}
},
}
保存代码后,我们可以看到,测试运行成功
渲染react元素
渲染react元素的目的是将hello,world最终显示在浏览上。所以需要测试dom, 现在打开react-dom/tests/react-dom.test.js,编写测试代码
const ReactDOM = require('..')
const React = require('../../react')
describe('react-dom', () => {
test('hello world', function () {
// 创建root节点
const root = document.createElement('root')
// 创建虚拟dom树
const element = React.createElement('h1', {}, 'hello, world')
// 渲染虚拟dom树
ReactDOM.render(element, root)
// 明确我们想要的结果
expect(root.innerHTML).toBe('<h1>hello, world</h1>')
})
})
保存测试代码后,在packages/react-dom目录下运行yarn test:watch,我们可以看到报错如下:
这是因为jest的测试环境默认是node,为了能测试dom,我们需要修改测试环境为jsdom
// 在packages/react-dom目录下创建jest.config.js
touch jest.config.js
// 编辑jest.config.js
{
"testEnviroment": "jsdom"
}
// 在packages/react-dom目录下重新运行测试
yarn test:watch
大功告成~
接着,保存代码,可以看到测试结果如下:
现在,我们根据测试代码的结果,来拆解任务
1. 首先我们的react-dom需要一个render方法
2. 调用render方法后
1. 根据type,创建一个dom节点
2. 为dom添加children节点
3. 在root中插入dom节点
打开packages/react-dom/lib/react-dom.js,编写代码如下:
module.exports = {
render: function (element, root) {
const { type, props } = element
const children = props.children
// 第一步创建dom
const node = document.createElement(type)
// 第二步为dom添加children
if (typeof children == 'string') {
// 创建节点
const childNode = document.createTextNode(children)
// 插入节点
node.appendChild(childNode)
}
// 第三步在root中插入dom
root.appendChild(node)
},
}
测试结果,通过~
现在我们已经实现了最简单的react,但是目前的代码还不支持渲染复杂的虚拟dom树,也不支持为dom添加属性,如class,style等,详情请见第二篇文章: 【7天造一个react】第二天,支持渲染复杂虚拟DOM树
写在最后
- 如果你觉得这篇本文还不错,欢迎🔥点赞 + 关注 + 转发🔥~~~
- 如果本项目帮助到了你,请给仓库一颗⭐️
你的支持是我持续创造的动力!!!