前言:为什么我们要了解React的源码呢?
首先,拜读大神的代码是菜鸟进步最快的方法,从源码中学习别人的代码逻辑和规范,提升自己的整体编码能力;
其次,了解源码才能在排查bug时更快的知道问题根源;
最后,现在不懂点源码,出去面试肯定过不了,比如我…
总之,了解源码对职业发展的好处是大大的。
学习视频链接:慕课网-React源码深度解析
React的优势
为什么React很牛呢?作者提到了以下几点:
- React中的UI渲染如下:UI = fn(x),利用state控制数据,数据映射成UI的方式,屏蔽了大量的DOM操作。
- React的纯粹性:React的核心API只有 setState,没有vued的双向绑定等其他API,改变UI只能通过setState。
- React16重写了大量核心代码,但用户完全无感知。对大佬的崇拜指数型上升。
- React16中引入——Fiber,能有效解决JS单线程运行时,计算量太大造成的交互卡顿问题。
React基础API
React和React-DOM的区别
我刚开始用React时,经常分不清React、React-DOM,不懂两者之间的区别。只知道react-dom
提供了API——ReactDOM.render()
;但书写React
代码时,用到的API只需从react
中引用即可。
其实,react
核心代码只有1000行,而react-dom
的核心代码有2w多行,大部分逻辑都在react-dom
中,那react
核心代码库到底提供了什么呢?
react
的源码极少,只定义节点和表现行为。 react-dom
控制页面的渲染、更新。
React核心代码库
本文引用的react版本是截止到2020.1.8最新的代码库github/react代码库
CreateElement
JSX的产物到底是什么?原React语法如下:
function NavDemo() {
return (
<ul className="nav">
<li className="item" key="1">导航1</li>
<li className="item" key="2">导航2</li>
</ul>
);
}
export default class CreateElementDemo extends React.Component {
render() {
return <div className="demo-wrap"
key="demo"
style={{
color: 'red',
fontSize: '20px'
}}
>
<p style={{margin: '20px 0'}}>React.CreateElement</p>
<NavDemo />
{Demo}
</div>;
}
}
React
会将HTML标签<>解析为React.CreateElement(type, config, children)
,上面的实例解析后的代码如下所示:
export const Demo = React.createElement(
'div',
{
className: 'demo-wrap',
key: 'demo',
style: {
color: 'green',
fontSize: '30px'
}
},
React.createElement(
'p',
{
className: 'title'
},
'标题'
),
React.createElement(
NavDemo,
null,
''
)
);
React
书写规范要求:原生HTML
节点为小写,自定义的组件必须以大写字母开头。React.CreateElement()
接收的第一个参数type
,就是标签名,会将小写type
转为字符串,将大写type
转为一个变量存储。
下面来看React.createElement
的源码,
真正对我们有用的只有如下几个函数:
- createElement
- cloneElement
- hasValidRef:ref === undefined?
- hasValidKey:key === undefined?
- RESERVED_PROPS:React中的保留props,包含key 、ref、__self、__source
- isValidElement:判断是否是 REACT_ELEMENT_TYPE
// ReactElement的源码
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
?typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: ref,
props: props,
_owner: owner,
};
return element;
};
createElement
- 参数:type,config,children
- type:节点类型:是原生节点(string类型),还是一个组件(对象类型),还存在一些其他情况—React的原生组件(Fragment等)
- Config:过滤内置属性key,ref,__self,__source后,然后将其他属性存成一个数组
- Children:可以有多个兄弟节点,第三个及之后的元素都被视作children,各个子元素都会用React.createElement重新渲染。
- 声明defaultProps时,需要处理对应的默认值,如果是undefined,则采用默认值,传null的时候,不用默认值哦!
- 返回值:
ReactElement对象
ReactElement()
会将接收到的参数,组合为一个对象,直接返回,但会添加一个元素?typeof
?typeof
很重要,标识Element
是什么类型,写JSX
时,所有的html
节点都是通过React.CreateElement
创建的,都是REACT_ELEMENT_TYPE
。- 但会存在例外情况如
React.createPortal
就不是REACT_ELEMENT_TYPE
,后面会介绍
- 如何与DOM关联:后面会讲到
Component/PureComponent
import ReactNoopUpdateQueue from './ReactNoopUpdateQueue';
const emptyObject = {};
function Component(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
Component.prototype.isReactComponent = {};
Component.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};
- Component(props,context,updater)
- 只是一个构造函数,将接收的属性作为自己的属性,同时给Component添加一个空对象refs
- 不包含任何生命周期函数!
- Component原型链上的函数
- isReactComponent:{}
- setState(partialState, callback)
- forceUpdate(callback)
- setState和froceUpdate()调用的都是Component对象上的
updater.enqueueForceUpdate()
,该方法在react-dom中实现
function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;
// 此处和Component的构造函数相同
function PureComponent(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;
- PureComponent(props, context, updater)
- PureComponent的原理同上,只是PureComponent的原型链指向了Component.prototype
Ref
React中的Ref分为三种基本类型:String类型的Ref(即将废弃),回调函数类型的Ref,React.createRef()
- React.createRef()
- react源码中该函数的实现如下,我也很难相信!!!
- 此处给自己提个问题 Ref给我们的这个对象,在后期如何使用?如何将DOM挂载呢?实现应该会在后面的react-dom源码中解释
export function createRef() {
const refObject = {
current: null,
};
return refObject;
forwardRef
- 作用:可以把props.ref作为参数,传递给要被包装的子组件(只能是函数式组件)
- 用处:经常会应用在高阶函数中,此处就不展开讲了,具体可以去React官网中了解
import {REACT_FORWARD_REF_TYPE} from 'shared/ReactSymbols';
export default function forwardRef(
render: (props, ref) => React$Node,
) {
return {
?typeof: REACT_FORWARD_REF_TYPE,
render,
};
}
React.forwardRef()
返回的对象包含属性?typeof、render
,但此处的?typeof
替换的不是ReactElement
的?typeof
,而是替换React.Element
的type
属性中的?typeof
通过React.createElement
创建的所有组件,它的?typeof
都是REACT_ELEMENT_TYPE
,不要搞混了哦!这里很重要!
Context
- 作用:避免props的多层次嵌套,让子孙组件可以快速获取父组件传递的props
import {REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
import {ReactContext} from 'shared/ReactTypes';
export function createContex (
defaultValue,
calculateChangedBits: ?(a, b) => number,
) {
// 该组件有多个父级的context时,计算新老context叠加后的值
if (calculateChangedBits === undefined) {
calculateChangedBits = null;
}
const context: ReactContext<T> = {
// 此处的?typeof替换的仍然是React.Element.type的?typeof,而不是 React.Element.?typeof
?typeof: REACT_CONTEXT_TYPE,
_calculateChangedBits: calculateChangedBits,
// React中最多支持两个同时渲染的render,ReactNative和Fabric 或者 ReactDOM和React ART
_currentValue: defaultValue,
_currentValue2: defaultValue, rendering.
Provider: (null: any),
Consumer: (null: any),
};
context.Provider = {
?typeof: REACT_PROVIDER_TYPE,
_context: context,
};
// Consumer= context自身,所以当context中的值变化时,consumer就能直接通过_currentValue直接拿到值
context.Consumer = context;
return context;
}
ConcurrentMode
- 作用:React16之后提出的一个概念呢,可以让渲染过程更可控,让浏览器优先执行级别较高的任务
// 使用Demo,复制粘贴后,在React项目中可直接运行
// 当async变化时,注意观察动画和num值的变化
// 动画
.mode-wrap {
.move-wrap {
animation: move 3s ease-in 4s infinite;
}
@keyframes move {
0% {
transform: translateX(0);
}
50% {
transform: translateX(200px);
}
100% {
transform: translateX(0);
}
}
}
// 组件
import React, {ConCurrentMode} from 'react';
import {flushSync} from 'react-dom';
export default class ConCurrentModeDemo extends React.Component {
demoDom = null;
interval = null;
constructor(props) {
super(props);
this.state = {
async: true,
num: 1,
length: 1000
};
}
componentDidMount() {
this.interval = setInterval(() => {
this.updateNum();
}, 1000);
}
componentWillUnmount() {
if (this.interval) {
clearInterval(this.interval);
}
}
updateNum = () => {
let {num} = this.state;
const newNum = num === 3 ? 0 : num + 1;
if (this.state.async) {
this.setState({
num: newNum
});
} else {
// 提升setState的优先级
flushSync(() => {
this.setState({
num: newNum
});
});
}
}
handleChange(e) {
}
render() {
let {length, num, async} = this.state;
const children = [];
for (let i = 0; i < length; i++) {
children.push(
<div style={{width: '20px', height: '20px', background: '#9c92ec', color: 'white', display: 'inline-block'}} key={i}>
{num}
</div>
);
}
return <div className="mode-wrap">
<p style={{margin: '20px 0', fontSize: '20px'}}>ConCurrentMode, FlushSync</p>
<input type="checkbox" onChange={() => flushSync(() => this.setState({async: !async}))} checked={async} />强制优先更新num
<div style={{width: '600px', height: '400px'}} className="move-wrap">
{children}
</div>
</div>;
}
}
最后
Context、Children、其他的React源码稍后更新,请持续关注哦!