7. 函数组件的底层渲染机制
React 中的组件化开发:
没有明确全局和局部的概念「可以理解为都是局部组件,不过可以把组件注册到 React 上,这样每个额组件中只要导入 React 即可使用」
- 函数组件
- 类组件
- 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'}} />
如果设置的属性值不是字符串格式,需要基于 {} 进行嵌套。
渲染机制:
-
基于 babel-preset-react-app 把调用的组件转换为
createElement格式React.createElement(DemoOne, { title: "\u6211\u662F\u6807\u9898", x: 10, data: [100, 200], className: "box", style: { fontSize: '20px' } }); -
执行
createElement函数,创建出一个 Virtual DOM 对象 -
基于
root.render把 Virtual DOM 对象转换为真实 DOM 对象type值不再是一个字符串,而是一个函数了,此时:- 把 Virtual DOM 对象 中的
props作为实参传递给函数 - 执行函数
DemoOne - 接收函数执行的返回结果「也就是当前组件的 Virtual DOM 对象」
- 最后基于
root.render把组件返回的 Virtual DOM 对象转换为 真实 DOM 对象,插入到#root容器中
- 把 Virtual DOM 对象 中的
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 = ... // 不报错