【7天造一个react】第一天,2步带你say hello

192 阅读5分钟

不积跬步,无以至千里;不积小流,无以成江海

⚙️为什么要造轮子

  1. 你是否用了很久react,依然只停留于会用react框架上
  2. 你是否看了很多遍react源码解析的文章,对react的了解依然半知半解
  3. 你是否在开发中,遇到react的问题,都不知如何下手分析,导致无法精准定位问题
  4. 你是否看每次想要下定决心开始学习react源码时,看到它那数万行代码,心里就开始打退堂鼓,想着明日再说🙊

明日复明日,明日何其多。蓦然回首,你依然停留在原地。

看百遍,不如做一遍

通过本系列文章的学习,

  1. 你将学会打造一个相对完整的react,从而让你对react的底层原理有更深入的了解
  2. 你将学会使用TDD的方式编程

为什么是TDD

如果你问我有什么方法可以让你的编码能力有极大的提升?🦄

那我一定推荐你去了解TDD,很多人对TDD都有偏见,认为TDD太麻烦,增加了工作量,本来写业务代码就已经花费很多时间了,还要花时间去写测试代码,白给自己增加工作量。但事实上,我认为通过TDD的方式编程其实是更高效的。这是因为大部分人遇到一个需求,还没想清楚就开始动手(想一想面试时候的你遇到手写代码题是否是这样),而不是使用以终为始的思维方式去解决问题。

  1. 明确结果: 定义&理解问题
  2. 拆解结果:拆解问题
  3. 给出解决方案

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树

写在最后

  • 如果你觉得这篇本文还不错,欢迎🔥点赞 + 关注 + 转发🔥~~~
  • 如果本项目帮助到了你,请给仓库一颗⭐️

你的支持是我持续创造的动力!!!