React--Component组件浅析

这篇具有很好参考价值的文章主要介绍了React--Component组件浅析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一 前言

在 React 世界里,一切皆组件,我们写的 React 项目全部起源于组件。组件可以分为两类,一类是类( Class )组件,一类是函数( Function )组件。

本章节,我们将一起探讨 React 中类组件和函数组件的定义,不同组件的通信方式,以及常规组件的强化方式,帮助你全方位认识 React 组件,从而对 React 的底层逻辑有进一步的理解。

二 什么是React组件?

想要理解 React 组件是什么?我们首先要来分析一下组件和常规的函数和类到底有什么本质的区别。

/* 类 */
class textClass {
    sayHello=()=>console.log('hello, my name is alien')
}
/* 类组件 */
class Index extends React.Component{
    state={ message:`hello ,world!` }
    sayHello=()=> this.setState({ message : 'hello, my name is alien' })
    render(){
        return <div style={{ marginTop:'50px' }} onClick={ this.sayHello } > { this.state.message }  </div>
    }
}
/* 函数 */
function textFun (){ 
    return 'hello, world'
}
/* 函数组件 */
function FunComponent(){
    const [ message , setMessage ] = useState('hello,world')
    return <div onClick={ ()=> setMessage('hello, my name is alien')  } >{ message }</div>
}

我们从上面可以清楚地看到,组件本质上就是类和函数,但是与常规的类和函数不同的是,组件承载了渲染视图的 UI 和更新视图的 setState 、 useState 等方法。React 在底层逻辑上会像正常实例化类和正常执行函数那样处理的组件。

因此,函数与类上的特性在 React 组件上同样具有,比如原型链,继承,静态属性等,所以不要把 React 组件和类与函数独立开来。

接下来,我们一起着重看一下 React 对组件的处理流程。

对于类组件的执行,是在react-reconciler/src/ReactFiberClassComponent.js中:

function constructClassInstance(
    workInProgress, // 当前正在工作的 fiber 对象
    ctor,           // 我们的类组件
    props           // props 
){
     /* 实例化组件,得到组件实例 instance */
     const instance = new ctor(props, context)
}

对于函数组件的执行,是在react-reconciler/src/ReactFiberHooks.js中

function renderWithHooks(
  current,          // 当前函数组件对应的 `fiber`, 初始化
  workInProgress,   // 当前正在工作的 fiber 对象
  Component,        // 我们函数组件
  props,            // 函数组件第一个参数 props
  secondArg,        // 函数组件其他参数
  nextRenderExpirationTime, //下次渲染过期时间
){
     /* 执行我们的函数组件,得到 return 返回的 React.element对象 */
     let children = Component(props, secondArg);
}

从中,找到了执行类组件和函数组件的函数。那么为了搞清楚 React 底层是如何处理组件的,首先来看一下类和函数组件是什么时候被实例化和执行的?

在 React 调和渲染 fiber 节点的时候,如果发现 fiber tag 是 ClassComponent = 1,则按照类组件逻辑处理,如果是 FunctionComponent = 0 则按照函数组件逻辑处理。当然 React 也提供了一些内置的组件,比如说 Suspense 、Profiler 等。

三 二种不同 React 组件

1 class类组件

类组件的定义

在 class 组件中,除了继承 React.Component ,底层还加入了 updater 对象,组件中调用的 setState 和 forceUpdate 本质上是调用了 updater 对象上的 enqueueSetState 和 enqueueForceUpdate 方法。

那么,React 底层是如何定义类组件的呢?

react/src/ReactBaseClasses.js

function Component(props, context, updater) {
  this.props = props;      //绑定props
  this.context = context;  //绑定context
  this.refs = emptyObject; //绑定ref
  this.updater = updater || ReactNoopUpdateQueue; //上面所属的updater 对象
}
/* 绑定setState 方法 */
Component.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
}
/* 绑定forceupdate 方法 */
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
}

如上可以看出 Component 底层 React 的处理逻辑是,类组件执行构造函数过程中会在实例上绑定 props 和 context ,初始化置空 refs 属性,原型链上绑定setState、forceUpdate 方法。对于 updater,React 在实例化类组件之后会单独绑定 update 对象。

|--------问与答---------|

问:如果没有在 constructor 的 super 函数中传递 props,那么接下来 constructor 执行上下文中就获取不到 props ,这是为什么呢?

/* 假设我们在 constructor 中这么写 */
constructor(){
    super()
    console.log(this.props) // 打印 undefined 为什么?
}

答案很简单,刚才的 Component 源码已经说得明明白白了,绑定 props 是在父类 Component 构造函数中,执行 super 等于执行 Component 函数,此时 props 没有作为第一个参数传给 super() ,在 Component 中就会找不到 props 参数,从而变成 undefined ,在接下来 constructor 代码中打印 props 为 undefined 。

/* 解决问题 */
constructor(props){ 
    super(props)
}

|---------end----------|

为了更好地使用 React 类组件,我们首先看一下类组件各个部分的功能:

class Index extends React.Component{
    constructor(...arg){
       super(...arg)                        /* 执行 react 底层 Component 函数 */
    }
    state = {}                              /* state */
    static number = 1                       /* 内置静态属性 */
    handleClick= () => console.log(111)     /* 方法: 箭头函数方法直接绑定在this实例上 */
    componentDidMount(){                    /* 生命周期 */
        console.log(Index.number,Index.number1) // 打印 1 , 2 
    }
    render(){                               /* 渲染函数 */
        return <div style={{ marginTop:'50px' }} onClick={ this.handerClick }  >hello,React!</div>
    }
}
Index.number1 = 2                           /* 外置静态属性 */
Index.prototype.handleClick = ()=> console.log(222) /* 方法: 绑定在 Index 原型链的 方法*/

上面把类组件的主要组成部分都展示给大家了。针对 state ,生命周期等部分,后续会有专门的章节进行讲解。

|--------问与答---------|

问:上述绑定了两个 handleClick ,那么点击 div 之后会打印什么呢?

答:结果是 111 。因为在 class 类内部,箭头函数是直接绑定在实例对象上的,而第二个 handleClick 是绑定在 prototype 原型链上的,它们的优先级是:实例对象上方法属性 > 原型链对象上方法属性。

|---------end----------|

对于 pureComponent 会在 React 渲染优化章节,详细探讨。

2 函数组件

ReactV16.8 hooks 问世以来,对函数组件的功能加以强化,可以在 function 组件中,做类组件一切能做的事情,甚至完全取缔类组件。函数组件的结构相比类组件就简单多了,比如说,下面写了一个常规的函数组件:

function Index(){
    console.log(Index.number) // 打印 1 
    const [ message , setMessage  ] = useState('hello,world') /* hooks  */
    return <div onClick={() => setMessage('let us learn React!')  } > { message } </div> /* 返回值 作为渲染ui */
 }
 Index.number = 1 /* 绑定静态属性 */

注意:不要尝试给函数组件 prototype 绑定属性或方法,即使绑定了也没有任何作用,因为通过上面源码中 React 对函数组件的调用,是采用直接执行函数的方式,而不是通过new的方式。

那么,函数组件和类组件本质的区别是什么呢?

对于类组件来说,底层只需要实例化一次,实例中保存了组件的 state 等状态。对于每一次更新只需要调用 render 方法以及对应的生命周期就可以了。但是在函数组件中,每一次更新都是一次新的函数执行,一次函数组件的更新,里面的变量会重新声明。

为了能让函数组件可以保存一些状态,执行一些副作用钩子,React Hooks 应运而生,它可以帮助记录 React 中组件的状态,处理一些额外的副作用。

四 组件通信方式

React 一共有 5 种主流的通信方式:

  1. props 和 callback 方式
  2. ref 方式。
  3. React-redux 或 React-mobx 状态管理方式。
  4. context 上下文方式。
  5. event bus 事件总线。

这里主要讲一下第1种和第5种,其余的会在对应章节详细解读。

① props 和 callback 方式

props 和 callback 可以作为 React 组件最基本的通信方式,父组件可以通过 props 将信息传递给子组件,子组件可以通过执行 props 中的回调函数 callback 来触发父组件的方法,实现父与子的消息通讯。

父组件 -> 通过自身 state 改变,重新渲染,传递 props -> 通知子组件

子组件 -> 通过调用父组件 props 方法 -> 通知父组件。

/* 子组件 */
function Son(props){
    const {  fatherSay , sayFather  } = props
    return <div className='son' >
         我是子组件
        <div> 父组件对我说:{ fatherSay } </div>
        <input placeholder="我对父组件说" onChange={ (e)=>sayFather(e.target.value) }   />
    </div>
}
/* 父组件 */
function Father(){
    const [ childSay , setChildSay ] = useState('')
    const [ fatherSay , setFatherSay ] = useState('')
    return <div className="box father" >
        我是父组件
       <div> 子组件对我说:{ childSay } </div>
       <input placeholder="我对子组件说" onChange={ (e)=>setFatherSay(e.target.value) }   />
       <Son fatherSay={fatherSay}  sayFather={ setChildSay }  />
    </div>
}

效果

React--Component组件浅析

⑤event bus事件总线

当然利用 eventBus 也可以实现组件通信,但是在 React 中并不提倡用这种方式,我还是更提倡用 props 方式通信。如果说非要用 eventBus,我觉得它更适合用 React 做基础构建的小程序,比如 Taro。接下来将上述 demo 通过 eventBus 方式进行改造。

import { BusService } from './eventBus'
/* event Bus  */
function Son(){
    const [ fatherSay , setFatherSay ] = useState('')
    React.useEffect(()=>{ 
        BusService.on('fatherSay',(value)=>{  /* 事件绑定 , 给父组件绑定事件 */
            setFatherSay(value)
       })
       return function(){  BusService.off('fatherSay') /* 解绑事件 */ }
    },[])
    return <div className='son' >
         我是子组件
        <div> 父组件对我说:{ fatherSay } </div>
        <input placeholder="我对父组件说" onChange={ (e)=> BusService.emit('childSay',e.target.value)  }   />
    </div>
}
/* 父组件 */
function Father(){
    const [ childSay , setChildSay ] = useState('')
    React.useEffect(()=>{    /* 事件绑定 , 给子组件绑定事件 */
        BusService.on('childSay',(value)=>{
             setChildSay(value)
        })
        return function(){  BusService.off('childSay') /* 解绑事件 */ }
    },[])
    return <div className="box father" >
        我是父组件
       <div> 子组件对我说:{ childSay } </div>
       <input placeholder="我对子组件说" onChange={ (e)=> BusService.emit('fatherSay',e.target.value) }   />
       <Son  />
    </div>
}

这样做不仅达到了和使用 props 同样的效果,还能跨层级,不会受到 React 父子组件层级的影响。但是为什么很多人都不推荐这种方式呢?因为它有一些致命缺点。

  • 需要手动绑定和解绑。
  • 对于小型项目还好,但是对于中大型项目,这种方式的组件通信,会造成牵一发动全身的影响,而且后期难以维护,组件之间的状态也是未知的。
  • 一定程度上违背了 React 数据流向原则。

五 组件的强化方式

①类组件继承

对于类组件的强化,首先想到的是继承方式,之前开发的开源项目 react-keepalive-router 就是通过继承 React-Router 中的 Switch 和 Router ,来达到缓存页面的功能的。因为 React 中类组件,有良好的继承属性,所以可以针对一些基础组件,首先实现一部分基础功能,再针对项目要求进行有方向的改造强化添加额外功能

基础组件:

/* 人类 */
class Person extends React.Component{
    constructor(props){
        super(props)
        console.log('hello , i am person')
    }
    componentDidMount(){ console.log(1111)  }
    eat(){    /* 吃饭 */ }
    sleep(){  /* 睡觉 */  }
    ddd(){   console.log('打豆豆')  /* 打豆豆 */ }
    render(){
        return <div>
            大家好,我是一个person
        </div>
    }
}
/* 程序员 */
class Programmer extends Person{
    constructor(props){
        super(props)
        console.log('hello , i am Programmer too')
    }
    componentDidMount(){  console.log(this)  }
    code(){ /* 敲代码 */ }
    render(){
        return <div style={ { marginTop:'50px' } } >
            { super.render() } { /* 让 Person 中的 render 执行 */ }
            我还是一个程序员!    { /* 添加自己的内容 */ }
        </div>
    }
}
export default Programmer

效果:

React--Component组件浅析

我们从上面不难发现这个继承增强效果很优秀。它的优势如下:

  1. 可以控制父类 render,还可以添加一些其他的渲染内容;
  2. 可以共享父类方法,还可以添加额外的方法和属性。

但是也有值得注意的地方,就是 state 和生命周期会被继承后的组件修改。像上述 demo 中,Person 组件中的 componentDidMount 生命周期将不会被执行。

②函数组件自定义 Hooks

在自定义 hooks 章节,会详细介绍自定义 hooks 的原理和编写。

③HOC高阶组件

在 HOC 章节,会详细介绍高阶组件 HOC 。

六 总结

从本章节学到了哪些知识:

  • 知道了 React 组件本质——UI + update + 常规的类和函数 = React 组件 ,以及 React 对组件的底层处理逻辑。
  • 明白了函数组件和类组件的区别。
  • 掌握组件通信方式。
  • 掌握了组件强化方式。

下一章节,我们将走进 React 状态管理 state 的世界中,一起探讨 State 的奥秘。文章来源地址https://www.toymoban.com/news/detail-477736.html

到了这里,关于React--Component组件浅析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 手写对象浅比较(React中pureComponent和Component区别)

            PureComponent会给类组件默认加一个shouldComponentUpdate这样的周期函数   在此周期函数中,它对新老的属性/状态 会做一个浅比较   如果经过浅比较,发现属性和状态并没有改变,则返回false(也就是不继续更新组件),有变化才会去更新!! 当使用component时,父组件的

    2024年02月16日
    浏览(31)
  • 【UE】大世界子关卡StreamingLevel加载流程源码浅析-虚幻4

    受限于硬件,当项目需要制作大世界的时候,整张大地图无法也没必要全部加载进内存。和所有支持大世界的引擎一样,UE采取了分块加载的方式:除了一个持久关卡(PersistentLevel)的加载以外,采用的都是运行时动态加载的方式,我们称这些关卡为子关卡或者流关卡(Stre

    2024年02月13日
    浏览(28)
  • 获取component组件的name名称

    import sgComponent from \\\"@/vue/components/sgComponent\\\"; sgComponent.name 就是组件的name 在组件自身内部console.log(this.$options.name) 可以获取自己的name 此外,this.$options.还可以获取data()、methods、created()、watch等等信息

    2024年02月13日
    浏览(30)
  • Unable to find node on an unmounted component in React

    小众错误一枚,网上都说需要react版本没有升级原因,因为是内部错误,控制台又无法定位到代码哪一行报错,网上又没有类似的解决方法,特此记录思路,供大家参考。 通过网上说的版本原因,合理推测是因为react 3.x版本对4.x写法的不兼容导致的,经过多部分排查改正,将

    2024年01月22日
    浏览(43)
  • Vue动态组件 component :is的使用

    vue 动态组件用于实现在指定位置上,动态加载不同的组件,核心代码为: componentTag 为自定义的变量,将需要加载的组件名赋值给它,即可在component /标签出现的位置,渲染该组件。 src/page/component1.vue src/page/component2.vue src/page/component3.vue 点击按钮组件1 点击按钮组件2 点击按

    2024年02月02日
    浏览(33)
  • antd List 滚动加载(InfiniteScroll ) react-infinite-scroll-component 重置滚动条

    问题 :在页面滚动的时候,infiniteScroll 页面数是自动+1。举个例子,页面加载到第三页,infiniteScroll 无法在重新开始计数,而在某些场景中需要重新开始计数,比如切换月份等(按照需求),page number 需要重新计数。(切换后会自动滚动到上一次滚动位置,自动调用接口)

    2024年02月22日
    浏览(27)
  • 微信小程序- component 自定义组件及引用

    一、自定义组件 1、根目录下创建一个 Components 文件夹 2、在 Components 文件夹中创建一个文件夹(文件夹名称为组件名称),例如组件名称为Myheader  3、点击Myheader 文件夹,右键选择 新建Component, 并命名为Myheader   4、在 Myheader.wxml 和 Myheader.scss 中写对应的内容 二、引用自定

    2024年02月04日
    浏览(30)
  • 小程序组件引用、子父组件传值、监听等详细介绍、component、observers

    目录 组件生命周期  组件引用 组件传值 父组件给子组件传值  子组件给父组件传值 observers 数据监听  注意事项 在介绍组件属性时,先介绍下组件的生命周期,可用的全部生命周期如下表所示: 生命周期 参数 描述 最低版本 created 无 在组件实例刚刚被创建时执行 1.6.3 att

    2024年02月06日
    浏览(27)
  • Vue3中使用component :is 加载组件

    1.不使用setup语法糖,这种方式和vue2差不多,is可以是个字符串 2. 使用setup语法糖,这时候的is如果使用字符串会加载不出来,得使用组件实例 第一种方式 第二种方式

    2024年02月16日
    浏览(33)
  • Angular 17+ 高级教程 – Component 组件 の Control Flow

      Control Flow 是 Angular v17 版本后推出的新模板语法,用来取代 NgIf、NgForOf、NgSwitch 这 3 个 Structure Directive。 Structure Directive 的好处是比较灵活,原理简单,但是即便用了微语法,它看上去还是相当繁琐,而且不够优雅。 Conrol Flow 的好处是它的语法够美,缺点是不必 Structure Di

    2024年03月11日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包