React相关基础知识

JSX

在JS中可以使用html的语法书写代码,是JS的一种扩展,通过JSX可以生成React元素。他在编译时会被Babel编译为React.createElement()方法的返回结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const App = ()=>{
return <h1 disabled>hello</h1>
}
// 会被Babel编译为这样一段代码
"use strict";
const App = () => {
return /*#__PURE__*/React.createElement("h1", {
disabled: true
}, "hello");
};
// 不过在React17之后有了新的JSX编译方法
function App() {
return _jsx('h1', { disabled: !0, children: "Hello" });
}

React.createElement方法返回如下结果

1
2
3
4
5
6
7
8
9
10
11
{
"type": "h1", // 元素类型
"key": null, // Diff算法使用 区分同一个父元素的不同子元素
"ref": null, // 真实DOM
"props": { // 属性
"disabled": true,
"children": "Hello" // 子元素
},
"_owner": null,
"_store": {}
}

Fiber

在新的React架构中,分为三个部分:Scheduler(调度器:调度任务的优先级),Reconciler(协调器:找出变化的组件),Renderer(渲染器:将变化的组件渲染到页面上)。而Reconciler内部就采用了Fiber的架构。

FIber并不是React特有的一个概念,与协程类似,也可以理解为协程的一种实现(在JavaScript中,协程的实现便是Generator 但相较于Generator,FIber可以更好实现高优更新),Fiber架构的提出源自于老的React架构中不能支撑异步更新。

当首屏渲染时,根据组件返回的JSX在内存中依次创建Fiber节点(保存对DOM的描述)并连接起来生成Fiber树,称为workInProgress FIber树(由于是首屏渲染 因此current Fiber为空)workInProgress FIber会尽量复用current FIber中的节点(Diff算法)当workInProgress FIber构建完成交给Renderer进行渲染,此时workInProgress FIber就会变为current FIber,React就是通过这个的方式进行DOM的更新。

在Fiber中使用了双缓存技术,在React中至多会存在两颗Fiber树,当前屏幕显示内容对应的成为current FIber,正在内存中构建的成为workInProgress FIber。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function App() {
const [num, add] = useState(0);
return (
<p onClick={() => add(num + 1)}>{num}</p>
)
}

8ReactDOM.render(<App/>, document.getElementById('root'));
//流程
//mount
1.首次执行ReactDOM.render会创建fiberRoot和rootFiber。其中fiberRoot是整个应用的根节点,rootFiber是<App/>所在组件树的根节点。
12之所以要区分fiberRoot与rootFiber,是因为在应用中我们可以多次调用ReactDOM.render渲染不同的组件树,他们会拥有不同的rootFiber。但是整个应用的根节点只有一个,那就是fiberRootNode。fiberRootNode的current会指向当前页面上已渲染内容对应Fiber树,即current Fiber树。

2.接下来进入render阶段,根据组件返回的JSX在内存中依次创建Fiber节点并连接在一起构建workInProgress Fiber。(下图中右侧为workInProgress Fiber,左侧为current Fiber
在构建workInProgress Fiber树时会尝试复用current Fiber树中已有的Fiber节点内的属性,在首屏渲染时只有rootFiber存在对应的current fiber(即rootFiber.alternate)。

3.图中右侧已构建完的workInProgress Fiber在commit阶段渲染到页面。

1
2
3
//update
1.当点击p节点触发状态改变,会重新进行render并构建一个新的workInProgress Fiber并尽可能复用current Fiber对应的节点数据。
2.workInProgress Fiber在render阶段完成构建后进入commit阶段渲染到页面上。渲染完毕后,workInProgress Fiber变为current Fiber

JSX与Fiber

JSX是一种描述当前组件内容的数据结构,他不包含组件schedule、reconcile、render所需的相关信息。例如:

  • 组件更新中的优先级
  • 组件的state
  • 组件被打上的用于Renderer的标记

在mount中,Reconciler根据JSX生成对应Fiber节点,在update中,Reconciler将JSX与Fiber节点保存的数据进行对比,生成新的FIber节点并根据结果为FIber打上标记。

函数组件

在React中,可以通过编写JS函数的形式来定义函数组件。它接受任意的入参props,并返回用于描述页面展示内容的React元素。

通过编写JS函数或者ES6的class来定义组件:

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
// 函数组件
const App = (props)=>{
return <h1>hello world</h1>
}
// babel编译后
const App = props => {
return React.createElement("h1", null, "hello world");
};
// 类组件
class App extends React.Component {
render() {
return <h1>hello world</h1>;
}
}
// babel编译后
16class App extends React.Component {
render() {
return React.createElement("h1", null, "hello world");
}
}
// 被当做组件调用
let a = <App name={'App'}/>;
// babel编译后
let a = React.createElement(App, {
name: 'App'
});

那么React怎么区分类组件和函数式组件呢?

类组件的父类(Component)的原型上存在isReactComponent属性,而函数式组件不存在该属性。

state

  • state的更新可能是异步的
  • 出于性能考虑可能会把多个setState合并成一个进行更新(事件处理函数结束后进行批量更新)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 控制是否批量更新
let isBatchingUpdate = false;
const queue = []; // 批量更新队列
let state = { number: 0 };
function setState(newState) {
if (isBatchingUpdate) {
queue.push(newState);
} else {
state = { ...state, ...newState };
}
}

function handleClick() {
isBatchingUpdate = true;
setState({ number: 1 });
console.log(state);
setState({ number: 2 });
console.log(state);
state = queue.reduce((newState, action) => {
return { ...newState, ...action };
}, state);
}
handleClick();
console.log(state);

事件处理

React会将JSX中书写的事件绑定到document/容器上,然后通过eventTarget的方式进行事件的触发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 通过这样一个方法将事件绑定到document上
function dispatchEvent(event) {
// 事件类型 对应的真实dom
let { type, target } = event;
const eventType = `on${type}`;
// 此时为批量更新模式
updateQueue.isBatchingUpdate = true;
// 合成事件对象
const syntheticEvent = createSyntheticEvent(event);
// 模拟冒泡
while (target) {
const { store } = target;
const hanlder = store && store[eventType];
// 将事件绑定到对应的dom元素上
hanlder && hanlder.call(target, syntheticEvent);
target = target.parentNode;
}

updateQueue.isBatchingUpdate = false;
updateQueue.batchUpdate();
}

ref

React提供了一种方式,允许访问DOM节点或者render方法创建的React元素。原理就时先初始化一个ref元素{current:null};然后在创建真实DOM的时候将current赋值为真实dom或者类组件的实例。

对于函数组件,由于执行后就会被销毁,不存在实例,因此需要使用其他方法获取到函数组件的ref(通过forwardRef包裹获取ref)

1
2
3
4
5
6
7
8
9
10
1function createDOM(vdom) {
const { type, props, ref } = vdom;
//文本节点 和 普通节点 组件 分别处理
let dom;
... // 根据节点类型 文本 原生元素 组件 创建DOM元素
vdom.dom = dom;
// 给ref赋值
if (ref) ref.current = dom;
return dom;
}

生命周期

  • old lifecycle
  • 挂载
    • constructor
    • componentWillMount
    • render
    • componentDidMount
  • 更新
    • componentWillReceiveProps
    • shouldComponentUpdate
    • componentWillUpdate
    • render
    • componentDidUpdate
  • 卸载
    • componentWillUnmount


React相关基础知识
https://jing-jiu.github.io/jing-jiu/2022/04/05/Framework/React/基础/
作者
Jing-Jiu
发布于
2022年4月5日
许可协议