React原理 JSX基础

JSX是什么

JSX是一种JavaScript的语法扩展,运用于React架构中,其格式比较像是模版语言,但事实上完全是在JavaScript内部实现的。元素是构成React应用的最小单位,JSX就是用来声明React当中的元素,React使用JSX来描述用户界面。

1
2
3
const A = ()=>{
return <div id='root'></div>
}

最后JSX变成什么

显然JSX并不是标准的JS语法,需要转换成标准的JS语法浏览器才能识别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function() {
React.createElement("div", {
style: {
height: 200
}
}, React.createElement("div", {
onClick: function(e) {
return console.log(e)
}
}), ["a", "b", "c"].map((function(t) {
return React.createElement("span", {
key: t
}, t)
}
)))
}

所有的JSX代码最终都会变成 React.createElement的函数调用

1
2
3
4
5
6
7
8
React.createElement({
// 组件类型 div等字符串
type,
// 一个对象,对应dom中的标签属性
[props],
// 子元素按原有顺序排列
[...children]
})

在老版本中由于编译后的代码会变成React.createElement的函数调用,所以必须在文件中引入React,17版以后

createElement 做了如下几件事

  • 处理config,把除了保留属性外的其他config赋值给props
  • 把children处理后赋值给props.children
  • 处理defaultProps
  • 调用ReactElement返回一个jsx对象(virtual-dom)

jsx对象上没有优先级、状态、effectTag等标记,这些标记在Fiber对象上,在mount时Fiber根据jsx对象来构建,在update时根据最新状态的jsx和current Fiber对比,形成新的workInProgress Fiber,最后workInProgress Fiber切换成current Fiber。

下面描述了JSX被转换之后,以及生成的type类型

1
2
3
4
5
6
7
8
9
10
<div style={{height:200}}>
<React.Fragment></React.Fragment> {/*Fragment组件*/}
string children {/*string类型子元素*/}
<FunctionComponent/> {/*函数式组件*/}
<ClassComponent/> {/*类组件组件*/}
{/*绑定属性*/}
<div onClick = {(e)=> console.log(e)}></div>
{/*数组类型子元素*/}
{['a','b','c'].map(item=> <span key={item}>{item}</span>)}
</div>

jsx元素类型 通过createElement转换 type属性
原生标签 react element类型 tagname 例如span div
fragment类型 react element类型 symbol react.fragment类型
文本类型 直接返回字符串
数组类型 返回数组,数组中的元素按上述规则转换
类组件 react element类型 类组件引用
函数组件 react element类型 函数组件引用
三元表达式 执行运算后的结果按上述规则匹配
函数调用 执行运算后的结果按上述规则匹配

React针对不同React element元素会产生不通的tag(种类),也就是不同的fiber对象, 在ReactWorkTags.js定义了tag种类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
export const FunctionComponent = 0;              // 函数组件
export const ClassComponent = 1; // 类组建
export const IndeterminateComponent = 2; // 在不知道是函数组件还是类组件的时候
export const HostRoot = 3; // Root Fiber 必须呗包含在另一个节点中
export const HostPortal = 4; // 通过createPortal创建的fiber子树,可能是另一个渲染的入口
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8; // <React.StrictMode>
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12; // <Profiler />
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const DehydratedFragment = 18;
export const SuspenseListComponent = 19;
export const ScopeComponent = 21;
export const OffscreenComponent = 22;
export const LegacyHiddenComponent = 23;
export const CacheComponent = 24;

最终JSX会变成由Fiber节点组成的链表

数组结构中的子节点会作为Fragment的子节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance
this.tag = tag;
this.key = key;
this.elementType = null;
this.type = null;
this.stateNode = null;

// Fiber
this.return = null; // 指向父级fiber节点
this.child = null; // 指向子级fiber节点
this.sibling = null; // 指向兄弟fiber节点
this.index = 0;

this.ref = null;

this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;

this.mode = mode;

// Effects
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;

this.lanes = NoLanes;
this.childLanes = NoLanes;

this.alternate = null;
}

对元素进行操作

  • React.Children.toArray 拍平子元素
1
2
3
4
5
6
7
8
9
10
11
12
const Element = (
<div style={{height:200}}>
<React.Fragment></React.Fragment>
string children
<FunctionComponent/>
<ClassComponent/>
<div onClick = {(e)=> console.log(e)}></div>
{['a','b','c'].map(item=> <span key={item}>{item}</span>)}
</div>
)

React.Children.toArray(Element.props.children)

  • 遍历子节点 React.Children.forEach React.Children.map

在 children 里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg。如果 children 是一个数组,它将被遍历并为数组中的每个子节点调用该函数。如果子节点为 null 或是 undefined,则此方法将返回 null 或是 undefined,而不会返回数组。

如果 children 是一个 Fragment 对象,它将被视为单一子节点的情况处理,而不会被遍历。

1
React.Children.map(children, function[(thisArg)])
  • React.Children.only

验证 children 是否只有一个子节点(一个 React 元素),如果有则返回它,否则此方法会抛出错误。

1
React.Children.only(children)
  • React.cloneElement 克隆元素

以 element 元素为样板克隆并返回新的 React 元素。config 中应包含新的 props,key 或 ref。返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。新的子元素将取代现有的子元素,如果在 config 中未出现 key 或 ref,那么原始元素的 key 和 ref 将被保留。

1
2
3
4
5
React.cloneElement(
element,
[config],
[...children]
)

React.cloneElement() 几乎等同于:

1
<element.type {...element.props} {...props}>{children}</element.type>

但是,这也保留了组件的 ref。这意味着当通过 ref 获取子节点时,你将不会意外地从你祖先节点上窃取它。相同的 ref 将添加到克隆后的新元素中。如果存在新的 ref 或 key 将覆盖之前的。

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2015-2025 SunZhiqi

此时无声胜有声!

支付宝
微信