一、关于 JSX 底层处理机制
第一步:把我们编写的 JSX 语法,编译为虚拟 DOM 对象「virtualDOM」
虚拟 DOM 对象:框架自己内部构建的一套对象体系(对象的相关成员都是 React 内部规定的),基于这些属性描述出,我们所构建视图中的,DOM 节点的相关特征!!
@1 基于 babel-preset-react-app
把 JSX 编译为 React.createElement(...)
这种格式!!
只要是元素节点,必然会基于
createElement
进行处理!React.createElement(ele,props,...children)
- ele:元素标签名「或组件」
- props:元素的属性集合(对象)「如果没有设置过任何的属性,则此值是 null」
- children:第三个及以后的参数,都是当前元素的子节点
@2 再把 createElement
方法执行,创建出 virtualDOM
虚拟 DOM 对象
也有将 virtualDOM 称之为:
JSX 元素
、JSX 对象
、ReactChild 对象
...
jsx
virtualDOM = {
$$typeof: Symbol(react.element),
ref: null,
key: null,
type: 标签名「或组件」,
// 存储了元素的相关属性 && 子节点信息
props: {
// 元素的相关属性,
// children:子节点信息「没有子节点则没有这个属性、属性值可能是一个值、也可能是一个数组」
}
}
第二步:把构建的 virtualDOM 渲染为真实 DOM
真实 DOM:浏览器页面中,最后渲染出来,让用户看见的 DOM 元素!!
基于
ReactDOM
中的render
方法处理的!!
v16
jsx
ReactDOM.render(<>...</>, document.getElementById("root"));
v18
jsx
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<>...</>);
补充说明:
-
第一次渲染页面是直接从
virtualDOM
->真实 DOM
; -
但是后期视图更新的时候,需要经过一个
DOM-DIFF
的对比,计算出补丁包PATCH
(两次视图差异的部分),把 PATCH 补丁包进行渲染!!
二、组件渲染机制
1. 函数组件
-
创建:在 SRC 目录中,创建一个 xxx.jsx 的文件,就是要创建一个组件;我们在此文件中,创建一个函数,让函数返回 JSX 视图「或者 JSX 元素、virtualDOM 虚拟 DOM 对象」;这就是创建了一个"函数组件"!!
-
调用:基于 ES6Module 规范,导入创建的组件「可以忽略.jsx 后缀名」,然后像写标签一样调用这个组件即可!!
-
命名:组件的名字,我们一般都采用 PascalCase「大驼峰命名法」这种方式命名
jsx
<Component/> // 单闭合调用
<Component> ... </Component> // 双闭合调用
- 调用组件的时候,我们可以给调用的组件设置(传递)各种各样的属性
jsx
<DemoOne
title="我是标题"
x={10}
data={[100, 200]}
className="box"
style={{ fontSize: "20px" }}
/>
-
如果设置的属性值不是字符串格式,需要基于"{}胡子语法"进行嵌套
-
调用组件的时候,我们可以把一些数据/信息基于属性 props 的方式,传递给组件!!
-
渲染机制
@1 基于 babel-preset-react-app
把调用的组件转换为 createElement
格式
jsx
React.createElement(DemoOne, {
title: "\u6211\u662F\u6807\u9898",
x: 10,
data: [100, 200],
className: "box",
style: {
fontSize: "20px",
},
});
@2 把 createElement
方法执行,创建出一个 virtualDOM
对象!!
jsx
var 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 容器中!!
2. 属性 props 的处理
-
调用组件,传递进来的属性是"只读"的「原理:props 对象被冻结了」
Object.isFrozen(props) -> true
获取:props.xxx
修改:props.xxx=xxx =>报错
-
作用:父组件(index.jsx)调用子组件(DemoOne.jsx)的时候,可以基于属性,把不同的信息传递给子组件;子组件接收相应的属性值,呈现出不同的效果,让组件的复用性更强!!
-
虽然对于传递进来的属性,我们不能直接修改,但是可以做一些规则校验
-
设置默认值
jsxComponent.defaultProps = { x: 0, // ...... };
-
设置其它规则,例如:数据值格式、是否必传... 「依赖于官方的一个插件:prop-types」
jsximport PropTypes from "prop-types"; FuncComponent.propTypes = { // 类型是字符串、必传 title: PropTypes.string.isRequired, // 类型是数字 x: PropTypes.number, // 多种校验规则中的一个 y: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]), };
传递进来的属性,首先会经历
规则校验
,不管校验成功还是失败,最后都会把属性给形参 props 只不过如果不符合设定的规则,控制台会抛出警告错误{不影响属性值的获取}!!
-
-
如果就想把传递的属性值进行修改,我们可以:
- 把 props 中的某个属性赋值给其他内容「例如:变量、状态...」
- 我们不直接操作 props.xxx=xxx,但是我们可以修改变量/状态值!!
三、知识扫盲
基于props特性的一些js底层知识扩展,对象的冻结、密封、扩展问题
1.冻结
冻结对象:Object.freeze(obj)
检测是否被冻结:Object.isFrozen(obj)
=>true/false
被冻结的对象:
- 不能修改成员值
- 不能新增成员
- 不能删除现有成员
- 不能给成员做劫持「Object.defineProperty」
2.密封
密封对象:Object.seal(obj)
检测是否被密封:Object.isSealed(obj)
被密封的对象:可以修改成员的值,但也不能删、不能新增、不能劫持!!
3.扩展
把对象设置为不可扩展:Object.preventExtensions(obj)
检测是否可扩展:Object.isExtensible(obj)
-
被设置不可扩展的对象:除了不能新增成员、其余的操作都可以处理!!
-
被冻结的对象,即是不可扩展的,也是密封的!!同理,被密封的对象,也是不可扩展的!!