React 函数组件渲染机制及props使用理解

945 阅读5分钟

函数组件概念

创建一个函数并返回jsx元素或者虚拟DOM对象

函数组件是静态组件,特性:

  • 不具备状态、生命周期函数、ref等内容
  • 第一次渲染完毕,除非父组件控制其重新渲染,否则内容不会再更新
  • 优势:渲染速度快
  • 弊端:静态组件,无法实现组件动态更新

组件创建

// 函数表达式
const DemoOne = function DemoOne(props) {
    return <div>DemoOne</div>
}
export default DemoOne;

// 或者
export default function DemoOne(props) {
    return <div>DemoOne</div>
}

组件调用

基于ES6Module规范,导入创建的组件「可以忽略.jsx后缀名」,然后像写标签一样调用这个组件即可

// 引入
import DemoOne from '@/views/DemoOne';

// 调用
<Component/> 单闭合调用
<Component> ... </Component> 双闭合调用

调用组件的时候,我们可以给调用的组件设置(传递)各种各样的属性

<DemoOne title="标题" x={10} data={[100, 200]} className="box" style={{ fontSize: '20px' }} />

如果设置的属性值不是字符串格式,需要基于“{}胡子语法”进行嵌套 调用组件的时候,我们可以把一些数据/信息基于属性props的方式,传递给组件!!

渲染机制

以下面这段代码编译过程为例:

// JSX
<DemoOne title="标题" x={10} data={[100, 200]} className="box" style={{ fontSize: '20px' }} />

// React.createElement()
React.createElement(Demo, {
    title: "\u6807\u9898",
    x: 10,
    data: [100, 200],
    className: "box",
    style: {
      fontSize: '20px'
    }
  })
 
 
// virtualDom 格式
{
    $$typeof: Symbol(react.element),
    key: null,
    props: {title: '标题', x: 10, data: Array(2), className: 'box', style: {…}},
    ref: null,
    type: ƒ Demo(props),
    _owner: FiberNode {tag: 0, key: null, stateNode: null, elementType: ƒ, type: ƒ, …},
    _store: {validated: false}
}

过程: @1 基于babel-preset-react-app把调用的组件转换为createElement格式

        React.createElement(DemoOne, {
            title: "\u6211\u662F\u6807\u9898",
            x: 10,
            data: [100, 200],
            className: "box",
            style: {
                fontSize: '20px'
            }
        })

@2 把createElement方法执行,创建出一个virtualDOM对象!!

        {
            $$typeof: Symbol(react.element),
            key: null,
            props: {title: '我是标题', x: 10, data: 数组, className: 'box', style: {fontSize: '20px'}}, //如果有子节点「双闭合调用」,则也包含children!!
            ref: null,
            type: DemoOne
        }

@3 基于root.render把virtualDOM变为真实的DOM

type值不再是一个字符串,而是一个函数了,此时:

  • 把函数执行 -> DemoOne()
  • 把virtualDOM中的props,作为实参传递给函数 -> DemoOne(props)
  • 接收函数执行的返回结果「也就是当前组件的virtualDOM对象」
  • 最后基于render把组件返回的虚拟DOM变为真实DOM,插入到#root容器中!!

图片.png

函数组件最后返回的结果是 virtualDOM

组件插槽

React组件了解及使用_react 容器组件内部也有创建创建组件

 这个就是一个最简单的函数组件

import React from 'react'
 
export default function Demo(props) {
    console.log("props", props)
    return (
        <div>
            Demo
            {props.children}
        </div>
    )
}

组件调用:

import Demo from './views/Demo';
 
 
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <div>
    <Demo title="标题" x={10} data={[100, 200]} className="box" style={{ fontSize: '20px' }} >
      123
      <span>4444</span>
    </Demo>
  </div>
);

给组件传值:

    {/* 传递属性: title 、x、data...  */}
    <Demo title="标题" x={10} data={[100, 200]} className="box" style={{ fontSize: '20px' }} >
    {/* 插槽传值 */}
      123
      <span>4444</span>
    </Demo>

运行结果:

图片.png

这里没有添加传递的属性值渲染,渲染内插槽内传递过来的内容

但是可以看到组件内props打印出来的值如下: 图片.png

内部相关的属性,都可以通过 props.xxx获取到 插槽:父组件内传递过来的内容都在props.children内可以获取到,然后对其进行渲染。

特性(静态组件)

主要用在在数据加载后,只渲染一次,无数据变动的组件,也称为傻瓜组件,无数据实时更新的组件即静态组件。

在第一次渲染完就不再变化的,可以使用函数组件

第一次渲染组件,把函数执行

  • 产生一个私有的上下文:EC(V)
  • 把解析出来的props「含children」传递进来「但是被冻结了」
  • 对函数返回的JSX元素「virtualDOM」进行渲染

当我们点击按钮的时候,会把绑定的小函数执行:

  • 修改上级上下文EC(V)中的变量
  • 私有变量值发生了改变
  • 但是“视图不会更新”

=>也就是,函数组件第一次渲染完毕后,组件中的内容,不会根据组件内的某些操作,再进行更新,所以称它为静态组件

=>除非在父组件中,重新调用这个函数组件「可以传递不同的属性信息」

真实项目中,有这样的需求:第一次渲染就不会再变化的,可以使用函数组件!!

但是大部分需求,都需要在第一次渲染完毕后,基于组件内部的某些操作,让组件可以更新,以此呈现出不同的效果!!==> 动态组件「方法:类组件、Hooks组件(在函数组件中,使用Hooks函数)」

例子:

import React from 'react'
 
export default function Vote(props) {
    const { title } = props
    let num = 1
 
    return (
        <div>
            <h3>{title}</h3>
            <p>{num}</p>
 
            <button onClick={() => {
                num++
                console.log("num", num)
            }}>click</button>
        </div>
    )
}

这里打印的num变化,但是页面不会加载变化的数据

图片.png

函数组件props

官网。GitHub - facebook/prop-types: Runtime type checking for React props and similar objects

props是组件在使用后,获取到传递到属性及插槽内等相关的内容,上面的组件调用内有传递的例子,可以看到传值及使用等操作 简单用法在调用内可以看到

props传递进来的属性是’只读‘的 【原理:props被冻结了】

看下方样例代码: 图片.png

可以看到,props是被冻结的状态

Object.isFrozen(props) -> true
获取:props.xxx
修改:props.xxx=xxx  =>报错

组件传值的作用:父组件调用子组件的时候,可以基于属性,把不同的信息传递给子组件;子组件接收相应的属性值,呈现出不同的效果,让组件的复用性更强!!

props校验

对于传递进来的属性,我们不能直接修改,但是可以做一些规则校验 设置默认值:

函数组件.defaultProps = {
   x: 0,
   ......
};

设置默认值生效只是存在于该值未传值,当有传值的情况,则defaultProps 不生效!

设置其它规则,例如:数据值格式、是否必传... 「依赖于官方的一个插件:prop-types

import PropTypes from 'prop-types';
函数组件.propTypes = {
   // 类型是字符串、必传
   title: PropTypes.string.isRequired,
   // 类型是数字
   x: PropTypes.number,
   // 多种校验规则中的一个
   y: PropTypes.oneOfType([
          PropTypes.number,
          PropTypes.bool,
      ])
};

传递进来的属性,首先会经历规则的校验,不管校验成功还是失败,最后都会把属性给形参props,只不过如果不符合设定的规则,控制台会抛出警告错误{不影响属性值的获取}!!

修改props内传递的值:

  • 把props中的某个属性赋值给其他内容「例如:变量、状态...」
  • 我们不直接操作props.xxx=xxx,但是我们可以修改变量/状态值!!

例子: 图片.png

运行结果:

图片.png