一篇必看的React文章

简介: 本文适合有 React 基础的小伙伴进阶学习

一、前言


本文基于开源项目:

https://github.com/facebook/react

进行简单的梳理,希望能够让读者一文搞懂React 机制。


1.React中文文档

   本文需要对React有所了解,如果暂时没看过React,可以点击下文,学习React基础再来阅读。

传送门: https://react.docschina.org/


二、简单的demo

 


   我们先来看个简单的demo

image.png


   上面是一个简单的例子,很容易猜到页面会删除“前端早茶”文字,以及一个按钮“点我关注”。

   接下来,我们删除 import React from 'react' 删除这行代码,然后仍要得到同样的效果,怎么实现呢?

   毫无疑问,我们需要实现自己的React,项目目录结构如下:

+-- src                 // 开发目录
|   +-- gdlz            // 存放自己实现react(文件名:广东靓仔)
|   |    +--index.js    // 自己react的逻辑
|   +--index.js         // 主页面


主页面index.js代码如下:

/*
 * @Author: 前端早茶
 * @Date: 2021-03-05 21:59:06
 * @LastEditTime: 2021-03-06 07:28:44
 * @Description: my react
 * @FilePath: /reactDemo/src/index.js
 */
import React from './gdlz'
const ReactDOM = React
class Child extends React.Component{
  constructor (props) {
    super(props)
    this.state = {
      count: 1
    }
  }
  handleClick = () => {
    this.setState({
      count: this.state.count + 1
    })
  }
  render () {
    return <div>
      <h2 onClick={this.handleClick}>{this.state.count}</h2>
    </div>
  }
}
ChildComponent = React.useComponent(Child)
function App (props){
  const [count, setCount] = React.useState(1)
  return <div id="container" className="front-end">
    <h1>{props.title}, {count}</h1>
    <button onClick={() => setCount(count+1)}>关注</button>
    <hr/>
    <ChildComponent></ChildComponent>
  </div>
}
let element = <App title="前端早茶" />
ReactDOM.render(element, document.getElementById('root'))


从上面的代码中,我们可以看到,主页面index里面有:

 React.Component

 React.useComponent

 React.useState

 ReactDOM.render


因此我们需要实现:

Component,

useComponent

useState,

render,


需要注意,createElement 是我们的基础,具体原因是因为React使用的是JSX,需要转为React.createElement的。


我截取了React官网的一个demo,大家会更直观


image.png


自己的React里面最后要导出内容如下:


image.png


三、createElement


/**
 * @description: createElement 函数
 * @param {*} type 类型
 * @param {*} props 参数
 * @param {array} children 子元素
 * @return {*} 对象
 */
function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map(child =>
        typeof child === "object"
          ? child
          : createTextElement(child)
      ),
    },
  }
/**
 * @description: createTextElement 函数
 * @param {*} text 文本,用于直接渲染文字
 * @return {*} 对象
 */
function createTextElement(text) {
  return {
    type: "TEXT",
    props: {
      nodeValue: text,
      children: [],
    },
  }
}


四、Component、useComponent


/**
 * @description: Component 组件函数
 * @param {*}
 * @return {*}
 */
class Component {
  constructor(props){
    this.props = props
  }
}
/**
 * @description: useComponent
 * @param {*} Component 传入的class组件
 * @return {*}
 */
function useComponent(Component){
  return function(props){
    const component = new Component(props)
    let [state, setState] = useState(component.state)
    component.props = props
    component.state = state
    component.setState = setState
    console.log(component)
    return component.render()
  }
}


五、useState


// 下一个单元任务
let nextUnitOfWork = null
// 工作中的fiber
let wipRoot = null
let currentRoot = null
let deletions = null
let wipFiber = null
let hookIndex = null
/**
 * @description: useState 函数
 * @param {*} init component.state
 * @return {*}
 */
function useState(init){
  const oldHook =
  wipFiber.base &&
  wipFiber.base.hooks &&
  wipFiber.base.hooks[hookIndex]
  const hook = {
    state: oldHook ? oldHook.state : init,
    queue: [],
  }
  const actions = oldHook ? oldHook.queue : []
  actions.forEach(action => {
    hook.state = action
  })
  const setState = action => {
    hook.queue.push(action)
    wipRoot = {
      dom: currentRoot.dom,
      props: currentRoot.props,
      base: currentRoot,
    }
    nextUnitOfWork = wipRoot
    deletions = []
  }
  wipFiber.hooks.push(hook)
  hookIndex++
  return [hook.state, setState]
}


六、render


// 设置全局 下一个任务单元
let nextUnitOfWork = null
/**
 * @description: render函数
 * @param {*} vdom 虚拟dom
 * @param {*} container 容器
 * @return {*}
 */
function render(vdom, container){
  // 工作中的fiber 
  wipRoot = {
    dom: container,
    props: {
      children: [vdom],
    },
    base: currentRoot,
  }
  deletions = []
  nextUnitOfWork = wipRoot
}

 

从这个render可以看到,我们里面有使用到下一个任务单元,很明显会涉及到协调调度children的逻辑。由于篇幅关系这里简单描述一下:


  任务调度reconcileChildren

 

 假如当前有任务,而且当前帧还没结束,我们获取下一个任务单元,并且记录起来。当没有下一个任务了,咋们提交root。在这当中我们会使用到window.requestIdleCallback()这个函数。其作用是在浏览器的空闲时段内调用的函数排队,函数一般会按先进先调用的顺序执行。关于这个函数的具体介绍,我们可以在MDN进行阅读。

这里稍微提一下,React17如果子节点是数组,会调用reconcileChildrenArray。


七、总结


   我们实现了一个自己的React,很明显我们有很多细节没有考虑进去。不过经过梳理我们现在对React有了一个清晰的认识,阅读源码的时候我们分块进行理解,化整为零~

相关文章
|
JavaScript 前端开发 中间件
【react入门手册】学习react-redux,看这篇文章就够啦!
【react入门手册】学习react-redux,看这篇文章就够啦!
133 0
|
前端开发
react项目实战学习笔记-学习29-获取文章列表
react项目实战学习笔记-学习29-获取文章列表
110 0
react项目实战学习笔记-学习29-获取文章列表
|
前端开发
react项目实战学习笔记-学习42-文章编辑功能
react项目实战学习笔记-学习42-文章编辑功能
83 0
react项目实战学习笔记-学习42-文章编辑功能
|
前端开发
react项目实战学习笔记-学习47-文章编辑
react项目实战学习笔记-学习47-文章编辑
77 0
|
前端开发
React 16.x折腾记 - (8) 基于React+Antd封装选择单个文章分类(从构建到获取)
随着管理的文章数量增多,默认的几个分类满足不了现状了,趁着重构的过程把相关的功能考虑进去 本来想自己从头写过一个,看了下Antd有内置该类型的控件了,就没必要自己造了 一般自己写,肯定优先考虑数组对象格式[{tagName:'a',value:1}]; Antd提供的是纯数组,[string,string],那如何不改变它提供的格式情况下拿到我们想要的! 拓展部分我们需要的东东,有兴趣的瞧瞧,没兴趣的止步..
290 0
|
分布式计算 前端开发 JavaScript
一篇包含了react所有基本点的文章
一篇包含了react所有基本点的文章
136 0
|
前端开发 数据可视化
React中使用Echarts实现数据可视化的小案例(基础文章)
React中使用Echarts实现数据可视化的小案例(基础文章)
741 0
React中使用Echarts实现数据可视化的小案例(基础文章)
|
Web App开发 JavaScript 前端开发
|
7月前
|
设计模式 前端开发 数据可视化
【第4期】一文了解React UI 组件库
【第4期】一文了解React UI 组件库
384 0
|
7月前
|
资源调度 前端开发 JavaScript
React 的antd-mobile 组件库,嵌套路由
React 的antd-mobile 组件库,嵌套路由
132 0