实现function component

本文详细介绍了如何在React中正确处理function组件,包括创建DOM节点、处理props、function组件的DOM提交和优化props传递。作者展示了如何封装函数组件和普通DOM节点的处理逻辑,以适应React的DOM更新机制。
摘要由CSDN通过智能技术生成

1. 在react里,用的最多的就是function component,我们现在来实现一下,定义一个Count的函数组件引入到视图,毋庸置疑会报错,因为之前并没有写针对function的逻辑。首先我们可以通过在createDom方法里通过log打印每次创建节点的type值,如图:

(1)通过上图,得知,我们获取节点类型既不是标签也不是TEXT_ELEMENT而是一个function函数类型。

(2)我们可以在performWorkOfUnit方法里,通过判断fiber.type的类型是否是function,如果不是function,则执行创建dom,处理props逻辑;在处理children时,f如果类型为function,我们则调用fiber.type()并将其转化为数组,反之则取fiber.props.children.

具体代码实现:

(3)接着我们需要处理提交代码commitWork时的情况,由于我们的function节点是没有dom的,所以在执行提交代码时,一定会报错,因为在处理将节点加入到父节点时,function类型调用后获取的dom节点其父节点的dom为null,所以我们需要采用向上查找的形式向其父级的父级找直到找到有dom的父级节点,这里需要注意,不可以写if判断,如果写if判断则只会往上查找一次,需要使用while循环,不断向上查找,找到后才会退出循环。将其节点添加到父节点里边。

具体代码实现:

function commitWork(fiber) {
  if(!fiber) return
  let fiberParent = fiber.parent
  // 向上不断查找
  while(!fiberParent.dom) {
    fiberParent = fiberParent.parent
  }
  if(fiber.dom) {
    fiberParent.dom.append(fiber.dom)
  }
  commitWork(fiber.child)
  commitWork(fiber.sibling)
}

(4)到此,我们就可以将我们的App.jsx更改函数类型,以及main.jsx

import React from "./core/React.js"

function Counter({num}) {
    return <div>count:{num}</div>
}


function App() {
    return (
        <div id="hi">
            hello world
            <Counter num={10}></Counter>
        </div>
    )
}

export default App
import React from "./core/React.js"
import ReactDom from './core/ReactDom.js'
import App from './App.jsx'

ReactDom.createRoot(document.querySelector('#app')).render(<App></App>)

现在这样的写法就和react彻底对齐了。

2. 处理function component的props传参

(1)我们想要在function组件里进行props传参,只需要在我们调用fiber.type时,将fiber.props传入即可,当然如果我们传的参数为number类型,是需要在createElement里边进行优化的。

代码如下:

children:children.map((child) => {
  const isTextNode = typeof child === 'string' || typeof child === 'number' 
    return isTextNode ? createTextNode(child) : child
})

(2)如果我们在App.js里多复制一个function组件,发现第二个并没有显示,因为在performWorkOfUnit方法尾部返回下一个要执行的任务,最后的逻辑有待优化。如果fiber.parent.sibling为空,则直接跳出,这里需要考虑到function组件情况,在处理fiber.parent时,采用while循环向上查找,如果fiber.sibling不存在则找fiber父级的sibling,直至找到。如果找不到,则说明树形结构已经完全转化为链表。可以进行统一提交代码渲染视图。

代码如下:

// 4.返回下一个要执行的任务
  if(fiber.child) {
    return fiber.child
  }
  
  let nextFiber = fiber
  while(nextFiber) {
      if(nextFiber.sibling) return nextFiber.sibling;
      nextFiber = nextFiber.parent
  }

3. 重构function component

(1)将处理function component和处理正常dom节点的分开封装起来。代码更加的简洁。

(2)完整代码如下:

function createElement(type,props,...children) {
    return {
        type,
        props:{
            ...props,
            children:children.map((child) => {
              const isTextNode = typeof child === 'string' || typeof child === 'number' 
                return isTextNode ? createTextNode(child) : child
            })
        }
    }
}

function createTextNode(text) {
    return {
        type:'TEXT_ELEMENT',
        props:{
        nodeValue:text,
        children:[]
        }
    }
}

function render(el,container) {
  nextWorkOfUnit = {
    dom:container,
    props:{
      children:[el]
    }
  }
  root = nextWorkOfUnit
}

let root = null
let nextWorkOfUnit = null
function workLoop(deadline) {
    let shouldYield = false;
    while(!shouldYield && nextWorkOfUnit) {
      nextWorkOfUnit = performWorkOfUnit(nextWorkOfUnit)
      shouldYield = deadline.timeRemaining() < 1
    }
    if(!nextWorkOfUnit && root) {
      commitRoot()
    }
    requestIdleCallback(workLoop)
}

function commitRoot() {
  commitWork(root.child)
  root = null
}

function commitWork(fiber) {
  if(!fiber) return
  let fiberParent = fiber.parent
  // 向上不断查找
  while(!fiberParent.dom) {
    fiberParent = fiberParent.parent
  }
  if(fiber.dom) {
    fiberParent.dom.append(fiber.dom)
  }
  commitWork(fiber.child)
  commitWork(fiber.sibling)
}

function createDom(type) {
  return type === 'TEXT_ELEMENT' ? 
    document.createTextNode("") : 
    document.createElement(type)
}

function updateProps(props,dom) {
  for (const key in props) {
    if(key !== 'children') {
      dom[key] = props[key]
    }
  } 
}

function initChildren(fiber,children) {
  let prevChild = null
  children.forEach((child,index) => {
    // 由于我们要给孩子节点定义父亲等属性,所以定义新的对象,这样可以不直接破坏之前vdom的结构
    const newFiber = {
      type:child.type,
      props:child.props,
      child:null,
      parent:fiber,
      sibling:null,
      dom:null
    }
    if(index == 0) {
      fiber.child = newFiber
    } else {
      prevChild.sibling = newFiber
    }
    prevChild = newFiber
  });
}

function updateFunctionComponent(fiber) {
  const children = [fiber.type(fiber.props)]
  // 3.转化链表 设置指针
  initChildren(fiber,children)
}

function updateHostComponent(fiber) {
  if(!fiber.dom) {
    // 1. 创建dom
    const dom = (fiber.dom = createDom(fiber.type))
    // fiber.parent.dom.append(dom)
    // 2. 处理props
    updateProps(fiber.props,dom)
  }
  const children = fiber.props.children
  
  // 3.转化链表 设置指针
  initChildren(fiber,children)
}


function performWorkOfUnit(fiber) {
  const isFunctionComponent = typeof fiber.type === 'function'
  if(isFunctionComponent) {
    updateFunctionComponent(fiber)
  } else {
    updateHostComponent(fiber)
  }
  
  // 4.返回下一个要执行的任务
  if(fiber.child) {
    return fiber.child
  }
  
  let nextFiber = fiber
  while(nextFiber) {
      if(nextFiber.sibling) return nextFiber.sibling;
      nextFiber = nextFiber.parent
  }
}

requestIdleCallback(workLoop)

const React = {
    createElement,
    render
}

export default React

  • 12
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
@component装饰器是Angular中用于定义组件的装饰器,它可以帮助我们创建可重用的组件,并将其添加到Angular的组件树中。下面是一个示例代码: ``` import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'My App'; } ``` 我们可以看到,在上面的代码中,@Component装饰器被应用于AppComponent类上。它接受一个对象作为参数,该对象包含组件的元数据,例如选择器、模板和样式等。 具体实现@component装饰器的过程如下: 1. 定义@Component装饰器 ``` export function Component(options: ComponentOptions): (target: Function) => void { return function (target: Function) { // 逻辑实现 } } ``` 2. 定义ComponentOptions接口 ``` export interface ComponentOptions { selector: string; templateUrl: string; styleUrls?: string[]; providers?: any[]; } ``` 3. 在@Component装饰器中实现逻辑 在@Component装饰器中,我们需要获取组件类的原型,然后将元数据添加到原型上。具体实现如下: ``` export function Component(options: ComponentOptions): (target: Function) => void { return function (target: Function) { const proto = target.prototype; proto.selector = options.selector; proto.templateUrl = options.templateUrl; proto.styleUrls = options.styleUrls || []; proto.providers = options.providers || []; } } ``` 在上面的代码中,我们将选择器、模板和样式等元数据添加到组件类的原型中。 总的来说,@Component装饰器的实现过程比较简单,它主要是通过修改组件类的原型来添加元数据。这种元编程的技术让我们可以更加灵活和可扩展的定义组件,并将其添加到Angular的组件树中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值