一篇必看的React文章

前端大神之路

共 4515字,需浏览 10分钟

 · 2021-03-06

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


作者:广东靓仔

一、前言

本文基于开源项目:

https://github.com/facebook/react

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

1.React中文文档

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

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

二、简单的demo

    

    我们先来看个简单的demo


0be3cb82f57f38ed53f916c9bda03f52.webp

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

    接下来,我们删除 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 = {
      count1
    }
  }
  handleClick = () => {
    this.setState({
      countthis.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,大家会更直观


af8d58f4e2486a8a60a330c7d361f85d.webp


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


34a825f757efd423ff49eddf69328fc1.webp


三、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有了一个清晰的认识,阅读源码的时候我们分块进行理解,化整为零~

关注前端早茶,我们一起学习,共同进步

浏览 16
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报