从头实现一个简易版React(一)

前端之家收集整理的这篇文章主要介绍了从头实现一个简易版React(一)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

写在开头

工作中使用react也很长一段时间了,虽然对它的用法,原理有了一定的了解,但是总感觉停留在表面。本着知其然知其所以然的态度,我试着去看了react源码,几天下来,发现并不能看懂,反而更加云里雾里了- -!。既然看不懂,那就看看社区前辈们写的一些源码分析文章以及实现思路吧,又这么过了几天,总算是摸清点思路,于是在参考了前辈们的基础上,实现了一个简易版的react。
这个系列我打算分为3节,第一节介绍下实现的思路以及结构,第二节讲渲染,第三节讲更新。

进入正题

众所周知,react的核心是Virtual DOM,所以,我们的思路也是围绕着Virtual DOM展开,包含Virtual DOM模型的建立,生命周期的管理,对比差异的diff算法,将Virtual DOM转化为原生DOM并展示的patch方法等,setState异步机制以及react合成事件由于还没有研究到,暂时先忽略,事件处理跟某位前辈的思路一样,也是使用jquery事件代替,这里我们主要以实现渲染,更新为主,相信你在看完这个系列后,能对react的运行原理有一定理解。
项目地址:https://github.com/LuSuguru/f...,以下的所有代码都是通过es6编写,切勿用在生产环境。

Virtual DOM的实现

React的一切都基于Virtual DOM,我们第一步自然先实现它,如下:

  1. /**
  2. * @param type :代表当前的节点属性
  3. * @param key :用来标识element,用于优化以后的更新
  4. * @param props:节点的属性
  5. */
  6. function VDom(type,key,props) {
  7. this.type = type
  8. this.key = key
  9. this.props = props
  10. }
  11. // 代码地址:src/react/reactElement.js

实现了vDom后,理所需要一个方法来将我们写的元素转化为vDom。一般我们都是JSX来创建元素的,但它只不过是React.createElment的语法糖。所以,接下来,我们要实现的就是createElement方法

  1. function createElement(type,config,...children) {
  2. const props = {}
  3.  
  4. config = config || {}
  5. // 获取key,用来标识element,方便以后高效的更新
  6. const { key = null } = config
  7. let propName = ''
  8.  
  9. // 复制config里的内容到props
  10. for (propName in config) {
  11. if (config.hasOwnProperty(propName) && propName !== 'key') {
  12. props[propName] = config[propName]
  13. }
  14. }
  15.  
  16. // 转化children
  17. if (children.length === 1 && Array.isArray(children[0])) {
  18. props.children = children[0]
  19. } else {
  20. props.children = children
  21. }
  22.  
  23. return new VDom(type,props)
  24. }
  25. // 代码地址:src/react/reactElement.js

这段代码也非常简单,根据我们传入的参数,生成对应的vDom

ReactComponent的实现

我们所创建的VDom类型分为3种:

  • 文本类型
  • 原生DOM类型
  • 自定义类型

不同的类型,肯定有不同的渲染和更新逻辑,我们把这些逻辑与vDom一起,封装成对应的ReactComponent类,通过ReactComponent类控制vDom,这里我把它们命名为ReactTextComponent,ReactDomComponent,ReactCompositeComponent,分别对应三种类型。
首先是基类ReactComponet:

  1. // component基类,用来处理不同的虚拟dom更新,渲染
  2. class Component {
  3. constructor(element) {
  4. this._vDom = element
  5. // 用来标识当前component
  6. this._rootNodeId = null
  7. }
  8. }
  9. // 代码地址:src/react/component/ReactComponent.js

接着再让不同类型的component继承这个基类,每种component类型都有mount和update两个方法,用来执行渲染和更新

  1. class ReactDomComponent extends ReactComponent {
  2. // 渲染
  3. mountComponent() {}
  4.  
  5. // 更新
  6. updateComponent() {}
  7. }
  1. class ReactCompositeComponent extends ReactComponent {
  2. // 渲染
  3. mountComponent() {}
  4.  
  5. // 更新
  6. updateComponent() {}
  7. }
  1. class ReactTextComponent extends ReactComponent {
  2. // 渲染
  3. mountComponent() {}
  4.  
  5. // 更新
  6. updateComponent() {}
  7. }

入口的实现

实现了ReactComponent后,我们自然需要一个入口去得到ReactComponent并调用它的mount。在使用React时,通常都是通过

  1. import React from 'react'
  2. import ReactDOM from 'react-dom'
  3.  
  4. class App extends React.Component {
  5. }
  6.  
  7. ReactDOM.render(<App />,document.getElementById('root'))

这段代码来充当渲染的入口,下面我们来实现这个入口,(为了方便说明,我把render方法也放在了React对象中)

  1. import Component from './Component'
  2. import createElement from './ReactElement'
  3. import instantiateReactComponent from './component/util'
  4. import $ from 'jquery'
  5.  
  6. const React = {
  7. nextReactRootIndex: 0,// 标识id,确定每个vDom的唯一性
  8. Component,// 所有自定义组件的父类
  9. createElement,// 创建vdom
  10.  
  11. render(vDom,container) { // 入口
  12. var componentInstance = instantiateReactComponent(vDom) //通过vDom生成Component
  13. var markup = componentInstance.mountComponent(this.nextReactRootIndex++)
  14.  
  15. container.innerHTML = markup
  16. $(document).trigger('mountReady')
  17. }
  18. }
  19. // 代码地址:src/react/index.js

由于渲染和更新都已经封装在不同的ReactComponent里,所以,这里也需要一个方法,根据不同的vDom类型生成对应的ReactComponent,下面我们就来实现这个方法

  1. // component工厂,用来返回一个component实例
  2. function instantiateReactComponent(node) {
  3. // 文本节点的情况
  4. if (typeof node === 'string' || typeof node === 'number') {
  5. return new ReactTextComponent(node)
  6. }
  7.  
  8. // 浏览器默认节点的情况
  9. if (typeof node === 'object' && typeof node.type === 'string') {
  10. return new ReactDomComponent(node)
  11. }
  12.  
  13. // 自定义的元素节点
  14. if (typeof node === 'object' && typeof node.type === 'function') {
  15. return new ReactCompositeComponent(node)
  16. }
  17. }

然后再调用入口ReactComponent的mount方法获取渲染内容,再将其渲染出来就行。

总结

以上就是实现一个react的总体思路,下节我们重点放在不同ReactComponet的mount上。
下一节地址:https://segmentfault.com/a/11...

参考资料,感谢几位前辈的分享
https://www.cnblogs.com/sven3...
https://github.com/purplebamb...陈屹 《深入React技术栈》

猜你在找的React相关文章