React18内核探秘:手写React高质量源码迈向高阶开发
核心代码,注释必读
// download:
3w ukoou com
React 和 ReactDOM 各自负责什么 react 负责描述特性,提供React API。
类组件、函数组件、hooks、contexts、refs...这些都是React特性,而 react 模块只描述特性长什么样、该怎么用,并不负责特性的具体实现。
react-dom 负责实现特性。
react-dom、react-native 称为渲染器,负责在不同的宿主载体上实现特性,达到与描述相对应的真实效果。比如在浏览器上,渲染出DOM树、响应点击事件等。
ReactDOM.render 的输入------ ReactElement
javascript
import React from 'react';
import ReactDOM from "./ReactDOM";
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
上面是一段常见的 React 代码。在项目的入口,人为显示地调用ReactDOM.render
,ReactDOM.render
接受 "根组件实例"和"挂载节点",然后进行内部逻辑转换,最终将DOM树渲染到挂载节点上。
那么,ReactDOM.render
拿到的 "根组件实例" 具体是什么?
组件实例其实是一个对象,以children
属性关联组件的父子关系。由React.createElement
函数创建。
ReactElement 是 React.createElement
的输出,ReactDOM.render
的输入,是 react 和 react-dom 之间最直观的联系。那么,我们来扒一扒这个数据结构。
我们一般会用JSX来描述组件结构,JSX本质上是一种语法扩展,通过Babel编译最终生成下面的语句:
css
React.createElement(
type,
[props],
[...children]
)
JSX最终将对组件的描述转换为对React.createElement
的调用。React.createElement
做了什么?
React.createElement
接受type
、props
、children
,然后进行一些操作:
- 处理
props
,从props
中提取出key
和ref
- 处理
children
,将children
以单体或者数组的形式附加到props
上 - 返回一个符合 ReactElement 数据结构的对象
如果用TypeScript简单描述 ReactElement 数据结构,它长这样👇
**
typescript
interface ReactElement {
$$typeof: Symbol | number; // 标识该对象是React元素,REACT_ELEMENT_TYPE = symbolFor('react.element') || 0xeac7,用Symbol获得一个全局唯一值
type: string | ReactComponent | ReactFragment
key: string | null
ref: null | string | object
props: {
[propsName: string]: any
children?: ReactElement | Array<ReactElement>
},
_owner: {
current: null | Fiber
}
}
React18内核探秘:手写React高质量源码迈向高阶开发 - PureComponent应用案例
React.PureComponent
类似于我们常用的 React.Component
,区别在于 PureComponent
的内置 shouldComponentUpdate
逻辑,它会同时对 props
和 state
的变化前和变化后的值进行浅对比 ,如果都没发生变化则会跳过重渲染,相当于多了一层 props
对比;下面通过一个简单的例子来对比这两种组件的效果差异;
效果对比
假设有一个计数器,点击按钮增加计数,并用两种组件渲染计数值:
scala
class Counter extends React.Component {
state = { count: 0 };
render() {
const { count } = this.state;
return (
<div style=>
<div>count: {count}</div>
<CountText count={count > 2 ? count : 0} />
<ConstText count={count > 2 ? count : 0} />
<button onClick={() => this.setState({ count: count + 1 })}>Add</button>
</div>
);
}
}
// 普通组件
class CountText extends React.Component {
render() {
const { count } = this.props;
console.log('normal rendered', count);
return <div>normal: {count}</div>;
}
}
// "纯"组件
class ConstText extends React.PureComponent {
render() {
const { count } = this.props;
console.log('pure rendered', count);
return <div>pure: {count}</div>;
}
}
javascript
import React from './react';
import ReactDOM from './react-dom';
class Greeting extends React.PureComponent {
render() {
console.log("Greeting was rendered at", new Date().toLocaleTimeString());
return <h3>Hello{this.props.name && ', '}{this.props.name}!</h3>;
}
}
// const Greeting = React.memo(function Greeting({ name }) {
// console.log("Greeting was rendered at", new Date().toLocaleTimeString());
// return <h3>Hello{name && ', '}{name}!</h3>;
// });
class MyApp extends React.Component {
constructor(props){
super(props)
this.state = {name: '', address: ''}
}
setName = (newName) => {
this.setState({name: newName})
}
setAddress = (newAddress) => {
this.setState({address: newAddress})
}
render(){
return <div>
<label>
Name{': '}
<input onInput={e => {
this.setName(e.target.value)
}} />
</label>
<label>
Address{': '}
<input onInput={e => {
this.setAddress(e.target.value)
}} />
</label>
<Greeting name={this.state.name} />
</div>
};
}
ReactDOM.render(<MyApp />, document.getElementById('root'));