B站2023年最新珠峰React全家桶(三)

7. 函数组件的底层渲染机制

React 中的组件化开发:

没有明确全局和局部的概念「可以理解为都是局部组件,不过可以把组件注册到 React 上,这样每个额组件中只要导入 React 即可使用」

  1. 函数组件
  2. 类组件
  3. Hooks 组件:在函数组件中使用 React Hooks 函数

7.1 函数组件

// /src/views/DemoOne.jsx
const DemoOne = function DemoOne() {
    return <div className="demo-box">
        我是 DemoOne
    </div>
}

export default DemoOne

在 src 目录下创建一个 xxx.jsx 文件,就表示要创建一个组件,我们在此文件中创建一个函数,让函数返回 JSX 视图「或者 JSX 元素、Virtual DOM 对象」,这就是创建了一个函数组件

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

// /src/index.jsx
import DemoOne from '@/views/DemoOne' // @ 表示 src 目录

root.render(
    <React.StrictMode>
        <DemoOne /> {/* 单闭合标签方式 */}
        <DemoOne></DemoOne> {/* 双闭合标签方式 */}
    </React.StrictMode>
);

单闭合标签方式和双闭合标签方式的区别:后者不仅可以传递属性,还可以传递子节点,在传递给函数的 props 中,有一个 children 属性存储这些子节点。

命名:组件的名字一般都采用大驼峰命名法。

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

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

如果设置的属性值不是字符串格式,需要基于 {} 进行嵌套。

渲染机制:

  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 函数,创建出一个 Virtual DOM 对象

  3. 基于 root.render 把 Virtual DOM 对象转换为真实 DOM 对象

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

    • 把 Virtual DOM 对象 中的 props 作为实参传递给函数
    • 执行函数 DemoOne
    • 接收函数执行的返回结果「也就是当前组件的 Virtual DOM 对象」
    • 最后基于 root.render 把组件返回的 Virtual DOM 对象转换为 真实 DOM 对象,插入到 #root 容器中

8. 关于 props 属性的细节知识

调用组件,传递进来的属性(props)是“只读的”,只能通过 props.xxx 来获取,但是通过 props.xxx = yyy 就会报错「这是因为 props 对象被劫持了」。

关于对象的规则设置:

  • 冻结
  • 密封
  • 不可扩展
let obj = {
    x: 10,
    y: 20
}
obj.x = 100 // 合法
obj.y = 200 // 合法
console.log(obj) // 合法
delete obj.y // 合法

// 冻结 -- 只读(不可修改)、不可扩展(不可增加属性)、不可删除、不可被 Object.defineProperty 劫持
Object.freeze(obj)
obj.x = 100 // 报错
obj.z = 300 // 报错
delete obj.y // 报错
Object.isFrozen(obj) // true -- 检测是否冻结

// 密封 -- 可修改、不可扩展(不可增加属性)、不可删除、不可被 Object.defineProperty 劫持
Object.seal(obj)
Object.isSealed(obj) // true -- 检测是否密封

// 不可扩展 -- 可修改、不可扩展(不可增加属性)、可删除、可被 Object.defineProperty 劫持
Object.preventExtensions(obj) // 使对象不可扩展
Object.isExtensible(obj) // false -- 检测是否可以扩展

被冻结的对象既是密封的,也是不可扩展的;

被密封的对象也是不可扩展的。

前面提到,调用组件的时候,可以给调用的组件设置(传递)各种各样的属性,当时的例子是:

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

在 /src/views/DemoOne.jsx 中,就可以通过 props 参数使用这些属性了,例如:

const DemoOne = function DemoOne(props) {
    let { className, style, title } = props
    {/* 这里通过模版字符串巧妙的将内部定义的属性和外部 props 传入的属性结合起来的 */}
    return <div className={`demo-box ${className}`} style={style}>
        <h2 className="title">{title}</h2>
    </div>
}

export default DemoOne

props 的作用:父组件调用子组件时,将不同的信息传递给子组件,子组件接收相应的属性值,呈现出不同的效果,让组件的复用性更强。

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

  • 设置默认值

    // 通过把函数作为对象,设置静态的私有属性方法,来给其设置属性的校验规则
    const DemoOne = function DemoOne(props) {
        let { title, x } = props
        return <div name={x}>
            <h2 className="title">{title}</h2>
        </div>
    }
    
    DemoOne.defaultProps = {
        x: 0 // 给 x 设置默认值 0
    }
    
  • 设置其他规则,例如:数据值格式、是否必传「依赖于官方的一个插件:prop-types」(安装命令为:npm install prop-types --save

    import PropTypes from "prop-types" // 导入
    
    const DemoOne = function DemoOne(props) {
        let { title, x } = props
        return <div name={x}>
            <h2 className="title">{title}</h2>
        </div>
    }
    
    DemoOne.defaultProps = {
        x: 0 // 给 x 设置默认值 0
    }
    
    DemoOne.propTypes = {
        title: PropTypes.string.isRequired, // 字符串、必传
        x: PropTypes.number // 数值
    }
    

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

PropTypes 的规则很多,但常用的只有类型判断和必传判断。

通过结构赋值是无法修改 props 对象的属性的,但是可以通过重新赋值来实现修改:

let x = props.x
x = ... // 不报错